To je dôvod, prečo musíme viazať obslužné rutiny udalostí v Class Components v React

Pri práci na Reacte ste sa určite museli stretnúť s kontrolovanými komponentmi a obslužnými rutinami udalostí. Tieto metódy musíme spojiť s inštanciou súčasti pomocou .bind()v konštruktore našej vlastnej súčasti.

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

V tomto článku sa chystáme zistiť, prečo to musíme urobiť.

Odporúčam prečítať si .bind()tu, ak ešte neviete, čo robí.

Obviňovať JavaScript, nereagovať

No, obviňovanie znie trochu tvrdo. Toto nie je niečo, čo musíme robiť kvôli spôsobu, akým React funguje, alebo kvôli JSX. Je to tak kvôli spôsobu, akým thisväzba funguje v JavaScripte.

Pozrime sa, čo sa stane, ak neviažeme metódu obsluhy udalosti na jej inštanciu súčasti:

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

Ak spustíte tento kód, kliknite na tlačidlo „Click Me“ a skontrolujte svoju konzolu. Na undefinedkonzole uvidíte tlačenú ako hodnotu thiszvnútra metódy obsluhy udalostí. handleClick()Metóda Zdá sa, že stratila kontextové (inštancia zložka) alebo thishodnoty.

Ako funguje „táto“ väzba v JavaScripte

Ako som už spomenul, deje sa to kvôli spôsobu, akým thisväzba funguje v JavaScripte. V tomto príspevku sa nebudem veľmi podrobne venovať, ale tu je skvelý zdroj na pochopenie toho, ako thisfunguje väzba v JavaScripte.

Pre našu diskusiu tu však hodnota relevantnej thisfunkcie závisí od toho, ako je táto funkcia vyvolaná.

Predvolená väzba

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

Toto je obyčajné volanie funkcie. Hodnota vo thisvnútri display()metódy je v tomto prípade okno alebo globálny objekt v neprípustnom režime. V prísnom režime je thishodnota undefined.

Implicitná väzba

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

Keď voláme funkciu týmto spôsobom - pred ktorou je kontextový objekt - thishodnota vo vnútri display()je nastavená na obj.

Ale keď priradíme tento odkaz na funkciu nejakej inej premennej a vyvoláme funkciu pomocou tohto nového odkazu na funkciu, dostaneme inú hodnotu thisvnútra display().

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

Vo vyššie uvedenom príklade, keď voláme outerDisplay(), neurčujeme kontextový objekt. Je to obyčajné volanie funkcie bez objektu vlastníka. V takom prípade hodnota thisinside display()klesne späť na predvolenú väzbu . Ukazuje na globálny objekt alebo undefinedak vyvolaná funkcia používa prísny režim.

Toto je obzvlášť použiteľné pri prenášaní takých funkcií, ako sú spätné volania, na inú vlastnú funkciu, funkciu knižnice tretej strany alebo zabudovanú funkciu JavaScriptu setTimeout.

Zvážte setTimeoutdefiníciu figuríny, ako je uvedené nižšie, a potom ju vyvolajte.

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

Môžeme prísť na to, že keď zavoláme setTimeout, JavaScript interne priradí obj.displayjeho argumentu callback.

callback = obj.display;

Táto operácia priradenia, ako sme už videli predtým, spôsobí, že display()funkcia stratí svoj kontext. Keď sa toto spätné volanie nakoniec vyvolá vo vnútri setTimeout, thishodnota vo vnútri display()spadne späť na predvolenú väzbu .

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

Explicitná tvrdá väzba

Aby sa tomu zabránilo, môžeme výslovne ťažké viažuthis hodnotu funkcie pomocou bind()metódy.

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

Teraz, keď voláme outerDisplay(), hodnota thisbodov objdovnútra display().

Aj keď prechádzame obj.displayako spätné volanie, thishodnota vo vnútri display()bude správne ukazovať na obj.

Obnovenie scenára iba s použitím JavaScriptu

Na začiatku tohto článku sme to videli v našej komponente React s názvom Foo. Ak sme neviazali obslužnú rutinu udalosti this, jej hodnota vo vnútri obslužnej rutiny udalosti bola nastavená na undefined.

Ako som už spomenul a vysvetlil, je to kvôli spôsobu, akým thisväzba funguje v JavaScripte, a nesúvisí s tým, ako funguje React. Odstráňte teda kód špecifický pre React a vytvorme podobný čistý príklad JavaScriptu na simuláciu tohto správania.

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

Nesimulujeme skutočné udalosti a obslužné rutiny, ale používame synonymný kód. Ako sme si všimli v príklade React Component, thishodnota bola, undefinedpretože kontext bol stratený po odovzdaní obslužnej rutiny ako spätného volania - synonymum pre operáciu priradenia. Toto tu sledujeme aj v tomto útržku kódu JavaScript, ktorý nereaguje.

"Počkaj minútu! Nemala by thishodnota smerovať na globálny objekt, pretože to prevádzkujeme v nie prísnom režime podľa pravidiel predvolenej väzby? “ môžete sa opýtať.

Nie. To je dôvod, prečo:

Telá deklarácií tried a výrazov tried sa vykonávajú v prísnom režime, to znamená konštruktorské, statické a prototypové metódy. Funkcie getra a setra sú vykonávané v prísnom režime.

Celý článok si môžete prečítať tu.

Aby sme zabránili chybe, musíme thishodnotu zviazať takto:

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

Toto nemusíme robiť v konštruktore a môžeme to robiť aj niekde inde. Zváž toto:

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

metóda obsluhy udalosti stráca svoj implicitne viazaný kontext. Keď dôjde k udalosti a obslužná rutina je vyvolaná, thishodnota klesne späť na predvolenú väzbu a je nastavená na undefined, pretože deklarácie triedy a prototypové metódy bežia v prísnom režime.

Keď naviažeme thisobslužnú rutinu udalosti na inštanciu súčasti v konštruktore, môžeme ju odovzdať ako spätné volanie bez obáv z straty kontextu.

Funkcie šípok sú z tohto správania vyňaté, pretože používajú lexikálnu thisväzbu, ktorá ich automaticky viaže na rozsah, v ktorom sú definované.