Zbog toga trebamo povezati rukovatelje događajima u komponentama klase u Reactu

Tijekom rada na Reactu, sigurno ste naišli na kontrolirane komponente i rukovatelje događajima. Te metode moramo povezati s instancom komponente koristeći .bind()konstruktor naše prilagođene komponente.

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") );

U ovom ćemo članku saznati zašto to trebamo učiniti.

Preporučio bih vam da pročitate .bind()ovdje ako već ne znate čime se bavi.

Kriviti JavaScript, ne reagirati

Pa, polaganje krivnje zvuči pomalo grubo. To nije nešto što moramo učiniti zbog načina na koji React radi ili zbog JSX-a. To je zbog načina na koji thisvezanje radi u JavaScript-u.

Pogledajmo što će se dogoditi ako ne vežemo metodu obrade događaja s instancom komponente:

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") );

Ako pokrenete ovaj kôd, kliknite gumb “Click Me” i provjerite svoju konzolu. Vidjet ćete undefinedispisanu na konzoli kao vrijednost thisiznutra metode obrade događaja. handleClick()Metoda čini se da je izgubio svoj kontekst (komponenta primjer) ili thisvrijednost.

Kako 'ovo' vezanje funkcionira u JavaScript-u

Kao što sam spomenuo, to se događa zbog načina na koji thisvezanje radi u JavaScript-u. Neću ulaziti u puno detalja u ovom postu, ali ovdje je sjajan resurs kako biste razumjeli kako thispovezivanje djeluje u JavaScript-u.

Ali relevantno za našu ovdje raspravu, vrijednost thisunutar funkcije ovisi o načinu na koji se ta funkcija poziva.

Zadano vezanje

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

Ovo je običan poziv funkcije. Vrijednost thisunutar display()metode u ovom je slučaju prozor - ili globalni - objekt u nesigurnom načinu. U strogom načinu rada thisvrijednost je undefined.

Implicitno vezivanje

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

Kada na taj način pozivamo funkciju - kojoj prethodi objekt konteksta - thisvrijednost unutar display()postavlja se na obj.

Ali kada dodijelimo ovu referencu funkciji nekoj drugoj varijabli i pozovemo funkciju pomoću ove nove reference funkcije, dobit ćemo različitu vrijednost thisiznutra display().

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

U gornjem primjeru, kada zovemo outerDisplay(), ne navodimo objekt konteksta. To je običan poziv funkcije bez vlasničkog objekta. U ovom slučaju, vrijednost thisiznutra display()vraća se na zadano vezanje . Ukazuje na globalni objekt ili undefinedako se funkcija koja se poziva koristi strogi način.

To se posebno odnosi na prosljeđivanje takvih funkcija kao što su povratni pozivi drugoj prilagođenoj funkciji, funkciji biblioteke treće strane ili ugrađenoj JavaScript funkciji poput setTimeout.

Razmislite o setTimeoutglupoj definiciji kao što je prikazano u nastavku, a zatim je pozovite.

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

Možemo shvatiti da kada zovemo setTimeout, JavaScript interno dodjeljuje obj.displaysvoj argument callback.

callback = obj.display;

Ova operacija dodjele, kao što smo vidjeli prije, uzrokuje da display()funkcija izgubi kontekst. Kada se ovaj povratni poziv na kraju pozove unutra setTimeout, thisvrijednost iznutra display()vraća se na zadano vezanje .

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

Eksplicitno tvrdo povezivanje

Da bi se to izbjeglo, možemo izričito tvrdi vežuthis vrijednost funkcije pomoću bind()metode.

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

Sada, kada zovemo outerDisplay(), vrijednost thisbodova prema objunutra display().

Čak i ako proslijedimo obj.displaykao povratni poziv, thisvrijednost unutar display()ispravno će ukazivati ​​na obj.

Ponovno stvaranje scenarija koristeći samo JavaScript

Na početku ovog članka vidjeli smo to u našoj React komponenti pod nazivom Foo. Ako nismo povezali obrađivač događaja s this, njegova vrijednost unutar obrađivača događaja postavljena je kao undefined.

Kao što sam spomenuo i objasnio, to je zbog načina na koji thisvezanje radi u JavaScript-u, a nije povezano s načinom na koji React radi. Zato uklonimo kod koji je specifičan za React i izradimo sličan čisti JavaScript primjer za simulaciju ovog ponašanja.

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

Ne simuliramo stvarne događaje i rukovatelje, već koristimo sinonimni kod. Kao što smo primijetili u primjeru React Component, thisvrijednost je undefinedbila izgubljena kada se kontekst izgubio nakon prosljeđivanja obrađivača kao povratnog poziva - sinonim za operaciju dodjele. To je ono što ovdje primjećujemo i u ovom JavaScript isječku koji nije React.

"Pričekaj minutu! Ne bi li thisvrijednost trebala ukazivati ​​na globalni objekt, budući da ga pokrećemo u nesigurnom načinu prema pravilima zadanog vezivanja? " mogli biste pitati.

Ne. Evo zašto:

Tijela klase deklaracijama i klase izraza izvršavaju u strogom načinu, odnosno graditelju, statičke i prototip metode. Funkcije getera i setera izvršavaju se u strogom načinu.

Cijeli članak možete pročitati ovdje.

Dakle, da bismo spriječili pogrešku, vrijednost moramo povezati thisovako:

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

To ne trebamo raditi u konstruktoru, a to možemo učiniti i negdje drugdje. Uzmite u obzir ovo:

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

the event handler method loses its implicitly bound context. When the event occurs and the handler is invoked, the this value falls back to default binding and is set to undefined , as class declarations and prototype methods run in strict mode.

When we bind the this of the event handler to the component instance in the constructor, we can pass it as a callback without worrying about it losing its context.

Arrow functions are exempt from this behavior because they use lexicalthisbinding which automatically binds them to the scope they are defined in.