Ezért kell bekötnünk az eseménykezelőket a React osztálykomponensekbe

A React-en való munka közben biztosan találkozott ellenőrzött alkatrészekkel és eseménykezelőkkel. Ezeket a módszereket hozzá kell kötnünk az összetevő példányhoz .bind(), az egyedi komponens konstruktorunkban.

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

Ebben a cikkben megtudjuk, miért kell ezt tennünk.

Azt javaslom, hogy olvassa el .bind()itt, ha még nem tudja, mit csinál.

A JavaScript hibáztatása, a nem reagál

Nos, a hibáztatás kissé durván hangzik. Ezt nem a React működése vagy a JSX miatt kell megtenni. Ennek oka a thiskötés működése a JavaScript-ben.

Nézzük meg, mi történik, ha nem kötjük össze az eseménykezelő metódust az összetevő példányával:

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

Ha futtatja ezt a kódot, kattintson a „Click Me” gombra, és ellenőrizze a konzolt. A undefinedkonzolra nyomtatva az thiseseménykezelő módszer belsejében látható értékként jelenik meg . A handleClick()módszer úgy tűnik, hogy elvesztette a kontextus (komponens például), vagy thisérték.

Hogyan működik ez az összerendelés a JavaScript-ben

Mint említettem, ez a thiskötés JavaScriptben történő működése miatt történik . Nem részletezem részletesen ebben a bejegyzésben, de itt van egy nagyszerű forrás annak megértéséhez, hogy a thiskötés hogyan működik JavaScriptben.

De az itt folyó vitánk szempontjából lényeges, hogy a thisfüggvény belsejének értéke attól függ, hogyan hívják meg ezt a függvényt.

Alapértelmezett kötés

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

Ez egy egyszerű függvényhívás. A módszer thisbelsejének értéke display()ebben az esetben az ablak - vagy a globális - objektum nem szigorú módban. Szigorú módban az thisérték undefined.

Implicit kötés

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

Ha ilyen módon meghívunk egy függvényt - amelyet egy kontextus objektum előz meg -, akkor a thisbenne display()lévő értékre állítjuk obj.

De amikor ezt a függvény hivatkozást hozzárendeljük valamilyen más változóhoz, és ennek az új függvény hivatkozásnak a használatával hívjuk meg a függvényt, akkor egy másik értéket kapunk thisbelülről display().

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

A fenti példában, amikor hívunk outerDisplay(), nem adunk meg egy kontextus objektumot. Ez egy egyszerű függvényhívás, tulajdonosi objektum nélkül. Ebben az esetben a thisbelső értéke display()visszaáll az alapértelmezett kötésre . Rámutat a globális objektumra, vagy undefinedha a meghívott függvény szigorú módot használ.

Ez különösen akkor alkalmazható, amikor olyan funkciókat továbbítanak, mint a visszahívások egy másik egyéni függvényhez, egy harmadik féltől származó függvénykönyvtár-funkcióhoz vagy egy beépített JavaScript-függvényhez, például setTimeout.

Vegye figyelembe a setTimeoutdummy definíciót az alábbiak szerint, majd hívja meg.

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

Kideríthetjük, hogy amikor hívunk setTimeout, a JavaScript belsőleg hozzárendeli obj.displayaz argumentumát callback.

callback = obj.display;

Ez a hozzárendelési művelet, amint azt korábban láthattuk, a display()függvény elveszíti a kontextusát. Amikor végül visszahívják ezt a visszahívást setTimeout, a thisbelső érték display()visszaáll az alapértelmezett kötésre .

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

Kifejezett keménykötés

Ennek elkerülése érdekében a metódus segítségével kifejezetten keményen köthetjük az thisértéket egy függvényhez bind().

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

Most, amikor hívunk outerDisplay(), a belső thispontok értéke .objdisplay()

Még ha obj.displayvisszahívásként is átadjuk , a thisbelső érték display()helyesen fog mutatni obj.

A forgatókönyv visszaállítása csak JavaScript használatával

A cikk elején ezt láttuk az úgynevezett React komponensünkben Foo. Ha nem kötöttük össze az eseménykezelőt this, akkor annak értékét az eseménykezelőn belül állítottuk be undefined.

Mint említettem és elmagyaráztam, ez annak az oka, hogy a thiskötés működik a JavaScript-ben, és nem kapcsolódik a React működéséhez. Tehát távolítsuk el a React-specifikus kódot, és készítsünk egy hasonló tiszta JavaScript-példát ennek a viselkedésnek a szimulálására.

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

Nem a tényleges eseményeket és kezelőket szimuláljuk, hanem szinonim kódot használunk. Amint azt a React Component példában megfigyeltük, az thisérték az volt, undefinedamikor a kontextus elveszett, miután visszahívásként átadta a kezelőt - a hozzárendelési művelet szinonimája. Ezt figyeljük meg ebben a nem React JavaScript-kódrészletben is.

"Várj egy percet! Nem kellene, hogy az thisérték a globális objektumra mutasson, mivel ezt nem szigorú módban futtatjuk az alapértelmezett kötés szabályainak megfelelően? " kérdezheted.

Nem . Ezért:

Az osztálydeklarációk és osztálykifejezések törzseit szigorúan hajtják végre, vagyis a konstruktor, a statikus és a prototípus módszereket. A Getter és a Setter funkciókat szigorúan hajtják végre.

A teljes cikket itt olvashatja el.

Tehát a hiba megelőzése érdekében a következő thisértéket kell kötnünk :

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

Nincs szükségünk erre a konstruktorban, és ezt máshol is megtehetjük. Ezt fontold meg:

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.