de Saurabh Misra

Acesta este motivul pentru care trebuie să legăm gestionarii de evenimente în Componentele clasei în React

Acesta este motivul pentru care trebuie sa legam gestionarii de
Fotografie de fundal de Kaley Dykstra pe Unsplash, imagine cod sursă generată la carbon.now.sh

În timp ce lucrați la React, trebuie să fi dat peste componente controlate și manipulatoare de evenimente. Trebuie să legăm aceste metode de instanța componentă folosind .bind() în constructorul componentei noastre personalizate.

class Foo extends React.Component{
  constructor( props ){
    super( props );
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick(event){
    // your event handling logic
  }
  
  render(){
    return (
      <button type="button" 
      onClick={this.handleClick}>
      Click Me
      </button>
    );
  }
}

ReactDOM.render(
  <Foo />,
  document.getElementById("app")
);

În acest articol, vom afla de ce trebuie să facem acest lucru.

Aș recomanda să citiți despre .bind() aici dacă nu știi deja ce face.

Blame JavaScript, Not React

Ei bine, a da vina sună cam dur. Nu trebuie să facem acest lucru din cauza modului în care funcționează React sau din cauza JSX. Acest lucru se datorează modului în care this legarea funcționează în JavaScript.

Să vedem ce se întâmplă dacă nu legăm metoda de gestionare a evenimentelor cu instanța componentă a acesteia:

class Foo extends React.Component{
  constructor( props ){
    super( props );
  }
    
  handleClick(event){
    console.log(this); // 'this' is undefined
  }
    
  render(){
    return (
      <button type="button" onClick={this.handleClick}>
        Click Me
      </button>
    );
  }
}

ReactDOM.render(
  <Foo />,
  document.getElementById("app")
);

Dacă rulați acest cod, faceți clic pe butonul „Faceți clic pe mine” și verificați consola. Vei vedea undefined tipărit pe consolă ca valoare a this din interiorul metodei de gestionare a evenimentelor. handleClick() metoda pare să aibă pierdut contextul său (instanță componentă) sau this valoare.

Cum funcționează această legare în JavaScript

După cum am menționat, acest lucru se întâmplă din cauza modului this legarea funcționează în JavaScript. Nu voi intra în multe detalii în această postare, dar aici este o resursă excelentă pentru a înțelege cum this legarea funcționează în JavaScript.

Dar relevant pentru discuția noastră aici, valoarea this în interiorul unei funcții depinde de modul în care este invocată acea funcție.

Legare implicită

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

display(); 

Acesta este un apel simplu. Valoarea a this în interiorul display() metoda în acest caz este fereastra – sau obiectul global – în mod non-strict. În modul strict, this valoarea este undefined.

Legare implicită

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

obj.display(); // Saurabh 

Când numim o funcție în acest mod – precedată de un obiect contextual – this valoare în interior display() este setat sa obj.

Dar când atribuim această referință de funcție altei variabile și invocăm funcția folosind această nouă referință de funcție, obținem o valoare diferită de this interior display() .

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

În exemplul de mai sus, când sunăm outerDisplay(), nu specificăm un obiect context. Este un apel simplu de funcție fără obiect proprietar. În acest caz, valoarea lui this interior display() cade înapoi la legare implicită. Arată spre obiectul global sau undefined dacă funcția invocată folosește modul strict.

Acest lucru se aplică în special în timp ce treceți astfel de funcții precum apelurile de apel către o altă funcție personalizată, o funcție de bibliotecă terță parte sau o funcție JavaScript încorporată, cum ar fi setTimeout .

Considera setTimeout definiție falsă așa cum se arată mai jos, apoi invocați-o.

// A dummy implementation of setTimeout
function setTimeout(callback, delay){

   //wait for 'delay' milliseconds
   callback();
   
}

setTimeout( obj.display, 1000 );

Ne putem da seama când apelăm setTimeout, JavaScript atribuie intern obj.display la argumentul său callback .

callback = obj.display;

Această operațiune de atribuire, așa cum am văzut anterior, provoacă display() funcție de a-și pierde contextul. Când acest apel invers este invocat în cele din urmă în interior setTimeout, this valoare în interior display() cade înapoi la legare implicită.

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

// uh oh! global

Legare dură explicită

Pentru a evita acest lucru, putem se leagă explicit în mod greu this valoare pentru o funcție utilizând bind() metodă.

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

// Saurabh

Acum, când sunăm outerDisplay(), valoarea a this arata spre obj interior display() .

Chiar dacă trecem obj.display ca apel invers, this valoare în interior display() va indica corect obj .

Recreerea scenariului folosind numai JavaScript

La începutul acestui articol, am văzut acest lucru în componenta noastră React numită Foo . Dacă nu am legat gestionarul de evenimente cu this , valoarea sa în interiorul gestionarului de evenimente a fost setată ca undefined.

După cum am menționat și explicat, acest lucru se datorează modului this legarea funcționează în JavaScript și nu are legătură cu modul în care funcționează React. Deci, să eliminăm codul specific React și să construim un exemplu similar JavaScript pur pentru a simula acest comportament.

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

Nu simulăm evenimente și manipulatoare reale, ci folosim cod sinonim. După cum am observat în exemplul React Component, this valoarea a fost undefined deoarece contextul s-a pierdut după trecerea handlerului ca callback – sinonim cu o operațiune de atribuire. Aceasta este ceea ce observăm aici și în acest fragment JavaScript care nu reacționează.

“Așteptați un minut! Nu ar trebui this valoare indică obiectul global, din moment ce rulăm acest lucru în mod non-strict în conformitate cu regulile legării implicite? ” s-ar putea să întrebi.

Nu. De-aceea:

Corpurile declarații de clasă și expresii de clasă sunt executate în modul strict, acesta este metoda constructor, static și prototip. Funcțiile Getter și Setter sunt executate în mod strict.

Puteți citi articolul complet aici.

Deci, pentru a preveni eroarea, trebuie să legăm this valoare ca aceasta:

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

Nu trebuie să facem acest lucru în constructor și putem face acest lucru și în altă parte. Gandeste-te la asta:

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

Dar constructorul este cel mai optim și mai eficient loc pentru codificarea declarațiilor de legare a gestionarului de evenimente, având în vedere că aici are loc toată inițializarea.

De ce nu trebuie să legămthis’ pentru funcțiile Arrow?

Mai avem două modalități prin care putem defini gestionari de evenimente în interiorul unei componente React.

class Foo extends React.Component{
  handleClick = () => {
    console.log(this); 
  }
 
  render(){
    return (
      <button type="button" onClick={this.handleClick}>
        Click Me
      </button>
    );
  }
} 

ReactDOM.render(
  <Foo />,
  document.getElementById("app")
);
class Foo extends React.Component{
 handleClick(event){
    console.log(this);
  }
 
  render(){
    return (
      <button type="button" onClick={(e) => this.handleClick(e)}>
        Click Me
      </button>
    );
  }
}

ReactDOM.render(
  <Foo />,
  document.getElementById("app")
);

Ambele folosesc funcțiile săgeată introduse în ES6. Când se utilizează aceste alternative, gestionarul nostru de evenimente este deja legat automat de instanța componentă și nu este necesar să-l legăm în constructor.

Motivul este că, în cazul funcțiilor săgeată, this este legat lexical. Aceasta înseamnă că folosește contextul funcției de închidere – sau global – ca scop this valoare.

În cazul exemplului de sintaxă a câmpurilor de clasă publică, funcția săgeată este închisă în interiorul Foo class – sau funcția constructor – deci contextul este instanța componentă, ceea ce dorim.

În cazul funcției săgeată ca exemplu de apel invers, funcția săgeată este închisă în interiorul render() , care este invocată de React în contextul instanței componente. Acesta este motivul pentru care funcția săgeată va capta, de asemenea, același context și this valoarea din interiorul acestuia va indica corect instanța componentă.

Pentru mai multe detalii referitoare la lexical this obligatoriu, verificați această resursă excelentă.

Pentru a face scurtă o poveste lungă

În Componentele clasei din React, când trecem referința funcției de gestionare a evenimentelor ca un apel invers ca acesta

<button type="button" onClick={this.handleClick}>Click Me</button>

metoda de gestionare a evenimentelor își pierde implicit legat context. Când apare evenimentul și este invocat handler-ul, this valoarea revine la legare implicită și este setat la undefined , deoarece declarațiile de clasă și metodele de prototip rulează în mod strict.

Când legăm this al gestionarului de evenimente către instanța componentă din constructor, îl putem transmite ca apel invers fără să ne îngrijorăm că își pierde contextul.

Funcțiile săgeată sunt scutite de acest comportament deoarece le folosesc lexical this legare care le leagă automat de domeniul în care sunt definite.