Štai kodėl mes turime susieti įvykių tvarkytuvus „React“ klasės komponentuose

Dirbdami prie „React“, turite susidurti su valdomais komponentais ir įvykių tvarkytuvais. Šiuos metodus turime susieti su komponento egzemplioriumi, naudodami .bind()mūsų pasirinktinio komponento konstruktorių.

class Foo extends React.Component{ constructor( props ){ super( props ); this.handleClick = this.handleClick.bind(this); } handleClick(event){ // your event handling logic } render(){ return (  Click Me  ); } } ReactDOM.render( , document.getElementById("app") );

Šiame straipsnyje mes išsiaiškinsime, kodėl turime tai daryti.

Rekomenduočiau perskaityti .bind()čia, jei dar nežinote, ką tai daro.

Kaltinkite „JavaScript“, nereaguokite

Na, kaltės klojimas skamba kiek šiurkščiai. Tai nereikia daryti dėl „React“ veikimo būdo ar dėl JSX. Taip yra dėl to, kaip thisįrišimas veikia „JavaScript“.

Pažiūrėkime, kas atsitiks, jei nesusiejame įvykių apdorojimo metodo su jo komponento egzemplioriumi:

class Foo extends React.Component{ constructor( props ){ super( props ); } handleClick(event){ console.log(this); // 'this' is undefined } render(){ return (  Click Me  ); } } ReactDOM.render( , document.getElementById("app") );

Jei paleisite šį kodą, spustelėkite mygtuką „Spustelėkite mane“ ir patikrinkite savo konsolę. Jūs matysite undefinedatspausdintą konsolėje kaip thisįvykio tvarkytuvo metodo vertę . handleClick()Metodas atrodo prarasta jos kontekstą (komponentas instancijos teismas) arba thisvertę.

Kaip „šis“ susiejimas veikia „JavaScript“

Kaip jau minėjau, taip nutinka dėl to, kaip thisįrišimas veikia „JavaScript“. Šiame įraše nesigilinsiu į daug detalių, tačiau čia yra puikus šaltinis norint suprasti, kaip thisįrišimas veikia „JavaScript“.

Bet svarbi mūsų diskusijai čia, thisfunkcijos viduje vertė priklauso nuo to, kaip ta funkcija yra naudojama.

Numatytasis įrišimas

function display(){ console.log(this); // 'this' will point to the global object } display(); 

Tai paprastos funkcijos iškvietimas. Iš vertė thisviduje display()šiuo atveju metodas yra langas - ar pasaulio - objektas ne griežtas režimas. Griežtu režimu thisvertė yra undefined.

Numanomas įpareigojimas

var obj = { name: 'Saurabh', display: function(){ console.log(this.name); // 'this' points to obj } }; obj.display(); // Saurabh 

Kai tokiu būdu iškviečiame funkciją, prieš kurią yra konteksto objektas, thisviduje esanti vertė display()nustatoma kaip obj.

Bet priskirdami šią funkcijos nuorodą kitam kintamajam ir iškviesdami funkciją naudodami šią naują funkcijos nuorodą, gausime kitokią thisvidaus vertę display().

var name = "uh oh! global"; var outerDisplay = obj.display; outerDisplay(); // uh oh! global

Ankstesniame pavyzdyje, kai skambiname outerDisplay(), nenurodome konteksto objekto. Tai paprastos funkcijos iškvietimas be objekto savininko. Tokiu atveju thisvidinės vertės vertė display()vėl tampa įprasta . Jis nurodo visuotinį objektą arba undefinedjei naudojama funkcija naudoja griežtą režimą.

Tai ypač taikoma perduodant tokias funkcijas kaip atgaliniai skambučiai kitai pasirinktinei funkcijai, trečiosios šalies bibliotekos funkcijai arba integruotai „JavaScript“ funkcijai, pvz setTimeout.

Apsvarstykite setTimeoutmanekeno apibrėžimą, kaip parodyta žemiau, ir tada pasinaudokite juo.

// A dummy implementation of setTimeout function setTimeout(callback, delay){ //wait for 'delay' milliseconds callback(); } setTimeout( obj.display, 1000 );

Galime suprasti, kad kai skambiname setTimeout, „JavaScript“ viduje priskiria obj.displaysavo argumentą callback.

callback = obj.display;

Ši priskyrimo operacija, kaip matėme anksčiau, lemia display()funkcijos praradimą. Kai šis atgalinis ryšys galiausiai yra iškviečiamas viduje setTimeout, jo thisvertė display()vėl grįžta į numatytąjį susiejimą .

var name = "uh oh! global"; setTimeout( obj.display, 1000 ); // uh oh! global

Aiškus kietasis įrišimas

Norėdami to išvengti, mes galime aiškiai sunku įpareigotithis vertė funkcija naudojant bind()metodą.

var name = "uh oh! global"; obj.display = obj.display.bind(obj); var outerDisplay = obj.display; outerDisplay(); // Saurabh

Dabar, kai skambiname outerDisplay(), thistaškų į objvidų vertė display().

Net jei perduosime obj.displaykaip atgalinį skambutį, thisviduje esanti vertė display()bus teisinga obj.

Scenarijaus atkūrimas naudojant tik „JavaScript“

In the beginning of this article, we saw this in our React component called Foo . If we did not bind the event handler with this , its value inside the event handler was set as undefined.

As I mentioned and explained, this is because of the way this binding works in JavaScript and not related to how React works. So let’s remove the React-specific code and construct a similar pure JavaScript example to simulate this behavior.

class Foo { constructor(name){ this.name = name } display(){ console.log(this.name); } } var foo = new Foo('Saurabh'); foo.display(); // Saurabh // The assignment operation below simulates loss of context // similar to passing the handler as a callback in the actual // React Component var display = foo.display; display(); // TypeError: this is undefined

We are not simulating actual events and handlers, but instead we are using synonymous code. As we observed in the React Component example, the this value was undefined as the context was lost after passing the handler as a callback — synonymous with an assignment operation. This is what we observe here in this non-React JavaScript snippet as well.

"Palauk minutę! Ar thisvertė neturėtų rodyti visuotinio objekto, nes tai vykdome ne griežtu režimu pagal numatytojo įrišimo taisykles? “ galite paklausti.

Ne. Štai kodėl:

Klasių deklaracijų ir klasės posakių kūnai vykdomi griežtai, tai yra konstruktoriaus, statinio ir prototipo metodai. „Getter“ ir „seter“ funkcijos vykdomos griežtu režimu.

Visą straipsnį galite perskaityti čia.

Taigi, norėdami išvengti klaidos, turime susieti tokią thisvertę:

class Foo { constructor(name){ this.name = name this.display = this.display.bind(this); } display(){ console.log(this.name); } } var foo = new Foo('Saurabh'); foo.display(); // Saurabh var display = foo.display; display(); // Saurabh

Mums nereikia to daryti konstruktoriuje ir galime tai padaryti ir kažkur kitur. Apsvarstykite tai:

class Foo { constructor(name){ this.name = name; } display(){ console.log(this.name); } } var foo = new Foo('Saurabh'); foo.display = foo.display.bind(foo); foo.display(); // Saurabh var display = foo.display; display(); // Saurabh

But the constructor is the most optimal and efficient place to code our event handler bind statements, considering that this is where all the initialization takes place.

Why don’t we need to bind ‘this’ for Arrow functions?

We have two more ways we can define event handlers inside a React component.

  • Public Class Fields Syntax(Experimental)
class Foo extends React.Component{ handleClick = () => { console.log(this); } render(){ return (  Click Me  ); } } ReactDOM.render( , document.getElementById("app") );
  • Arrow function in the callback
class Foo extends React.Component{ handleClick(event){ console.log(this); } render(){ return (  this.handleClick(e)}> Click Me  ); } } ReactDOM.render( , document.getElementById("app") );

Both of these use the arrow functions introduced in ES6. When using these alternatives, our event handler is already automatically bound to the component instance, and we do not need to bind it in the constructor.

The reason is that in the case of arrow functions, this is bound lexically. This means that it uses the context of the enclosing function — or global — scope as its this value.

In the case of the public class fields syntax example, the arrow function is enclosed inside the Foo class — or constructor function — so the context is the component instance, which is what we want.

In the case of the arrow function as callback example, the arrow function is enclosed inside the render() method, which is invoked by React in the context of the component instance. This is why the arrow function will also capture this same context, and the this value inside it will properly point to the component instance.

For more details regarding lexical this binding, check out this excellent resource.

To make a long story short

In Class Components in React, when we pass the event handler function reference as a callback like this

Click Me

įvykių tvarkytojo metodas praranda netiesiogiai susietą kontekstą. Kai įvykis įvyksta ir iškviečiamas tvarkytuvas, thisreikšmė grįžta į numatytąjį susiejimą ir nustatoma į undefined, nes klasės deklaracijos ir prototipų metodai veikia griežtai.

Susieję thisįvykių tvarkytuvą su komponento egzemplioriumi konstruktoriuje, galime jį perduoti kaip atgalinį skambutį, nesijaudindami, kad jis praranda kontekstą.

Rodyklių funkcijoms šis elgesys netaikomas, nes jos naudoja leksinį thisįrišimą, kuris automatiškai susieja jas su apimtimi, kurioje jos yra apibrėžtos.