Aus diesem Grund müssen wir Ereignishandler in Klassenkomponenten in React binden

Während der Arbeit an React müssen Sie auf kontrollierte Komponenten und Ereignishandler gestoßen sein. Wir müssen diese Methoden an die Komponenteninstanz binden, indem .bind()wir sie im Konstruktor unserer benutzerdefinierten Komponente verwenden.

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

In diesem Artikel werden wir herausfinden, warum wir dies tun müssen.

Ich würde empfehlen, hier zu lesen .bind(), wenn Sie noch nicht wissen, was es tut.

JavaScript beschuldigen, nicht reagieren

Schuldzuweisungen klingen etwas hart. Dies ist aufgrund der Funktionsweise von React oder aufgrund von JSX nicht erforderlich. Dies liegt an der Funktionsweise der thisBindung in JavaScript.

Mal sehen, was passiert, wenn wir die Event-Handler-Methode nicht an ihre Komponenteninstanz binden:

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

Wenn Sie diesen Code ausführen, klicken Sie auf die Schaltfläche "Click Me" und überprüfen Sie Ihre Konsole. Auf undefinedder Konsole wird der Wert thisvon innerhalb der Ereignishandlermethode angezeigt. Die handleClick()Methode scheint ihren Kontext (Komponenteninstanz) oder Wert verloren zu haben this.

Wie 'diese' Bindung in JavaScript funktioniert

Wie bereits erwähnt, geschieht dies aufgrund der Funktionsweise der thisBindung in JavaScript. Ich werde in diesem Beitrag nicht auf viele Details eingehen, aber hier ist eine großartige Ressource, um zu verstehen, wie die thisBindung in JavaScript funktioniert.

Für unsere Diskussion hier ist jedoch relevant, dass der Wert thisinnerhalb einer Funktion davon abhängt, wie diese Funktion aufgerufen wird.

Standardbindung

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

Dies ist ein einfacher Funktionsaufruf. Der Wert thisinnerhalb der display()Methode ist in diesem Fall das Fenster- oder das globale Objekt im nicht strengen Modus. Im strengen Modus ist der thisWert undefined.

Implizite Bindung

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

Wenn wir eine Funktion auf diese Weise aufrufen - vorangestellt von einem Kontextobjekt - wird der thisWert darin display()gesetzt obj.

Wenn wir diese Funktionsreferenz jedoch einer anderen Variablen zuweisen und die Funktion mit dieser neuen Funktionsreferenz aufrufen, erhalten wir einen anderen Wert von thisinside display().

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

Im obigen Beispiel geben wir beim Aufruf outerDisplay()kein Kontextobjekt an. Es ist ein einfacher Funktionsaufruf ohne Eigentümerobjekt. In diesem Fall wird der Wert von thisinside display()auf die Standardbindung zurückgesetzt . Es zeigt auf das globale Objekt oder undefinedwenn die aufgerufene Funktion den strengen Modus verwendet.

Dies gilt insbesondere für die Übergabe von Funktionen wie Rückrufen an eine andere benutzerdefinierte Funktion, eine Bibliotheksfunktion eines Drittanbieters oder eine integrierte JavaScript-Funktion wie setTimeout.

Betrachten Sie die setTimeoutDummy-Definition wie unten gezeigt und rufen Sie sie dann auf.

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

Wir können herausfinden, dass setTimeoutJavaScript beim Aufruf intern obj.displayseinem Argument zuweist callback.

callback = obj.display;

Diese Zuweisungsoperation führt, wie wir zuvor gesehen haben, dazu, dass die display()Funktion ihren Kontext verliert. Wenn dieser Rückruf schließlich innerhalb von aufgerufen wird setTimeout, fällt der thisWert innerhalb display()auf die Standardbindung zurück .

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

Explizite harte Bindung

Um dies zu vermeiden, können wir den Wert mithilfe der Methode explizit festthis an eine Funktion bindenbind() .

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

Wenn wir jetzt aufrufen outerDisplay(), zeigt der Wert von thisnach objinnen display().

Selbst wenn wir obj.displayals Rückruf übergeben, zeigt der thisWert im Inneren display()korrekt auf obj.

Neuerstellung des Szenarios nur mit JavaScript

Am Anfang dieses Artikels haben wir dies in unserer React-Komponente namens gesehen Foo. Wenn wir den Ereignishandler nicht mit gebunden haben this, wurde sein Wert im Ereignishandler auf festgelegt undefined.

Wie ich bereits erwähnt und erklärt habe, liegt dies an der Funktionsweise der thisBindung in JavaScript und nicht an der Funktionsweise von React. Entfernen wir also den reaktionsspezifischen Code und erstellen ein ähnliches reines JavaScript-Beispiel, um dieses Verhalten zu simulieren.

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

Wir simulieren keine tatsächlichen Ereignisse und Handler, sondern verwenden auch Code. Wie wir im Beispiel "React Component" festgestellt haben, war der thisWert undefinedso, dass der Kontext verloren ging, nachdem der Handler als Rückruf übergeben wurde - synonym mit einer Zuweisungsoperation. Dies beobachten wir auch hier in diesem JavaScript-Snippet ohne Reaktion.

"Warte eine Minute! Sollte der thisWert nicht auf das globale Objekt verweisen, da wir dies im nicht strengen Modus gemäß den Regeln der Standardbindung ausführen? “ du könntest fragen.

Nein. Deshalb:

Die Körper von Klassendeklarationen und Klassenausdrücken werden im strengen Modus ausgeführt, dh in den Konstruktor-, statischen und Prototypmethoden. Getter- und Setter-Funktionen werden im strengen Modus ausgeführt.

Den vollständigen Artikel können Sie hier lesen.

Um den Fehler zu vermeiden, müssen wir den thisWert wie folgt binden :

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

Wir müssen dies nicht im Konstruktor tun, und wir können dies auch woanders tun. Bedenken Sie:

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.