
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 this
kö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 undefined
konzolra nyomtatva az this
esemé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 this
kö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 this
kötés hogyan működik JavaScriptben.
De az itt folyó vitánk szempontjából lényeges, hogy a this
fü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 this
belsejé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 this
benne 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 this
belü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 this
belső értéke display()
visszaáll az alapértelmezett kötésre . Rámutat a globális objektumra, vagy undefined
ha 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 setTimeout
dummy 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.display
az 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 this
belső é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ő this
pontok értéke .obj
display()
Még ha obj.display
visszahívásként is átadjuk , a this
belső é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 this
kö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, undefined
amikor 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 lexicalthis
binding which automatically binds them to the scope they are defined in.