Како написати Реацт компоненту без употребе класа или удица

Са изласком Реацт Хоокс-а видео сам много постова који упоређују компоненте класе са функционалним компонентама. Функционалне компоненте нису ништа ново у Реацт-у, међутим пре верзије 16.8.0 није било могуће створити компоненту са статусом која има приступ кукама животног циклуса користећи само функцију. Или је било?

Назовите ме педантом (многи то већ чине!), Али када говоримо о компонентама класе, технички говоримо о компонентама створеним функцијама. У овом посту бих желео да користим Реацт да демонстрирам шта се заправо догађа када пишемо наставу у ЈаваСцрипт-у.

Часови вс функције

Прво бих желео да укратко покажем како се они који се обично називају функционалним и класним компонентама међусобно повезују. Ево једноставне компоненте написане као класа:

class Hello extends React.Component { render() { return 

Hello!

} }

А овде је написано као функција:

function Hello() { return 

Hello!

}

Приметите да је функционална компонента само метода приказивања. Због тога ове компоненте никада нису могле да задрже своје стање или да изврше било какве нежељене ефекте на тачкама током свог животног циклуса. Од Реацт-а 16.8.0 било је могуће креирати функционалне компоненте са статусом захваљујући кукама, што значи да компоненту можемо претворити попут ове:

class Hello extends React.Component { state = { sayHello: false } componentDidMount = () => { fetch('greet') .then(response => response.json()) .then(data => this.setState({ sayHello: data.sayHello }); } render = () => { const { sayHello } = this.state; const { name } = this.props; return sayHello ? 

{`Hello ${name}!`}

: null; } }

У функционалну компоненту попут ове:

function Hello({ name }) { const [sayHello, setSayHello] = useState(false); useEffect(() => { fetch('greet') .then(response => response.json()) .then(data => setSayHello(data.sayHello)); }, []); return sayHello ? 

{`Hello ${name}!`}

: null; }

Сврха овог чланка није у расправљању да је један бољи од другог, јер на ту тему већ постоји стотине постова! Разлог за приказивање две горе наведене компоненте је како бисмо могли да будемо јасни шта Реацт заправо ради са њима.

У случају компоненте класе, Реацт креира инстанцу класе користећи newкључну реч:

const instance = new Component(props); 

Ова инстанца је објекат. Када кажемо да је компонента класа, оно што заправо мислимо је да је она објекат. Ова нова објектна компонента може имати своје стање и методе, од којих неке могу бити методе животног циклуса (рендер, цомпонентДидМоунт, итд.) Које ће Реацт позвати на одговарајућим тачкама током живота апликације.

Са функционалном компонентом, Реацт је само назива уобичајеном функцијом (јер је то обична функција!) И враћа или ХТМЛ или више компоненти Реацт.

Методе помоћу којих се обрађује стање компоненте и активирају ефекти у тачкама током животног циклуса компоненте, сада је потребно увести ако су потребне. Они у потпуности раде на основу редоследа којим их позива свака компонента која их користи, јер не знају која их је компонента позвала. Због тога куке можете позивати само на највишем нивоу компоненте и не могу се позивати условно.

Функција конструктора

ЈаваСцрипт нема часове. Знам да изгледа као да има часове, управо смо написали два! Али испод хаубе ЈаваСцрипт није језик заснован на настави, већ прототип. Предавања су додата са спецификацијом ЕЦМАСцрипт 2015 (која се такође назива ЕС6) и само су чистија синтакса постојеће функционалности.

Кренимо са преписивањем компоненте Реацт класе без употребе синтаксе класе. Ево компоненте коју ћемо поново створити:

class Counter extends React.Component { constructor(props) { super(props); this.state = { count: 0 } this.handleClick = this.handleClick.bind(this); } handleClick() { const { count } = this.state; this.setState({ count: count + 1 }); } render() { const { count } = this.state; return (  +1 

{count}

); } }

Ово приказује дугме које повећава бројач када се кликне, то је класика! Прво што треба да креирамо је функција конструктора, која ће изводити исте радње које constructorметода у нашој класи изводи, осим позива, superјер је то ствар само класе.

function Counter(props) { this.state = { count: 0 } this.handleClick = this.handleClick.bind(this); } 

Ово је функција коју ће Реацт позвати са newкључном речи. Када се функција позове са newњом, она се третира као функција конструктора; креира се нови објекат, на њега thisсе указује променљива и функција се извршава с новим објектом који се користи где год thisје поменут.

Даље, морамо да пронађемо дом за renderи handleClickметоде и за то треба да разговарамо о ланцу прототипа.

Прототип ланца

ЈаваСцрипт омогућава наслеђивање својстава и метода између објеката кроз нешто што је познато као ланац прототипа.

Па, кажем наследство, али заправо мислим на делегирање. За разлику од других језика са класама, где се својства копирају из класе у њене инстанце, ЈаваСцрипт објекти имају унутрашњу везу прототипа која упућује на други објекат. Када позовете методу или покушате да приступите својству објекта, ЈаваСцрипт прво проверава својство самог објекта. Ако га тамо не може пронаћи, онда проверава прототип објекта (везу до другог објекта). Ако и даље не може да га пронађе, онда проверава прототип прототипа и тако даље по ланцу док га или не пронађе или не остане без прототипа за проверу.

Уопштено говорећи, сви објекти у ЈаваСцрипт-у имају Objectврх прототипа; овако имате приступ методама као што су toStringи hasOwnPropertyна свим објектима. Ланац се завршава када се до објекта дође nullкао његовог прототипа, што је обично на Object.

Покушајмо на примеру да ствар буде јаснија.

const parentObject = { name: 'parent' }; const childObject = Object.create(parentObject, { name: { value: 'child' } }); console.log(childObject); 

Прво стварамо parentObject. Будући да смо користили синтаксу литерарног објекта на који ће овај објект бити повезан Object. Даље користимо Object.createза стварање новог објекта користећи parentObjectњегов прототип.

Сада, када користимо console.logза штампу childObject, требало би да видимо:

излаз конзоле детеОбјецт

The object has two properties, there is the name property which we just set and the __proto___ property. __proto__ isn't an actual property like name, it is an accessor property to the internal prototype of the object. We can expand these to see our prototype chain:

проширени излаз цхилдОбјецт

The first __proto___ contains the contents of parentObject which has its own __proto___ containing the contents of Object. These are all of the properties and methods that are available to childObject.

It can be quite confusing that the prototypes are found on a property called __proto__! It's important to realise that __proto__ is only a reference to the linked object. If you use Object.create like we have above, the linked object can be anything you choose, if you use the new keyword to call a constructor function then this linking happens automatically to the constructor function's prototype property.

Ok, back to our component. Since React calls our function with the new keyword, we now know that to make the methods available in our component's prototype chain we just need to add them to the prototype property of the constructor function, like this:

Counter.prototype.render = function() { const { count } = this.state; return (  +1 

{count}

); }, Counter.prototype.handleClick = function () { const { count } = this.state; this.setState({ count: count + 1 }); }

Static Methods

This seems like a good time to mention static methods. Sometimes you might want to create a function which performs some action that pertains to the instances you are creating - but it doesn't really make sense for the function to be available on each object's this. When used with classes they are called Static Methods. I'm not sure if they have a name when not used with classes!

We haven't used any static methods in our example, but React does have a few static lifecycle methods and we did use one earlier with Object.create. It's easy to declare a static method on a class, you just need to prefix the method with the static keyword:

class Example { static staticMethod() { console.log('this is a static method'); } } 

And it's equally easy to add one to a constructor function:

function Example() {} Example.staticMethod = function() { console.log('this is a static method'); } 

In both cases you call the function like this:

Example.staticMethod() 

Extending React.Component

Our component is almost ready, there are just two problems left to fix. The first problem is that React needs to be able to work out whether our function is a constructor function or just a regular function. This is because it needs to know whether to call it with the new keyword or not.

Dan Abramov wrote a great blog post about this, but to cut a long story short, React looks for a property on the component called isReactComponent. We could get around this by adding isReactComponent: {} to Counter.prototype (I know, you would expect it to be a boolean but isReactComponent's value is an empty object. You'll have to read his article if you want to know why!) but that would only be cheating the system and it wouldn't solve problem number two.

In the handleClick method we make a call to this.setState. This method is not on our component, it is "inherited" from React.Component along with isReactComponent. If you remember the prototype chain section from earlier, we want our component instance to first inherit the methods on Counter.prototype and then the methods from React.Component. This means that we want to link the properties on React.Component.prototype to Counter.prototype.__proto__.

Fortunately there's a method on Object which can help us with this:

Object.setPrototypeOf(Counter.prototype, React.Component.prototype); 

It Works!

That's everything we need to do to get this component working with React without using the class syntax. Here's the code for the component in one place if you would like to copy it and try it out for yourself:

function Counter(props) { this.state = { count: 0 }; this.handleClick = this.handleClick.bind(this); } Counter.prototype.render = function() { const { count } = this.state; return (  +1 

{count}

); } Counter.prototype.handleClick = function() { const { count } = this.state; this.setState({ count: count + 1 }); } Object.setPrototypeOf(Counter.prototype, React.Component.prototype);

As you can see, it's not as nice to look at as before. In addtion to making JavaScript more accessible to developers who are used to working with traditional class-based languages, the class syntax also makes the code a lot more readable.

I'm not suggesting that you should start writing your React components in this way (in fact, I would actively discourage it!). I only thought it would be an interesting exercise which would provide some insight into how JavaScript inheritence works.

Although you don't need to understand this stuff to write React components, it certainly can't hurt. I expect there will be occassions when you are fixing a tricky bug where understanding how prototypal inheritence works will make all the difference.

Надам се да вам је овај чланак био занимљив и / или угодан. Више постова које сам написао на свом блогу можете пронаћи на хеллоцоде.дев. Хвала вам.