
Докато работите по React, трябва да сте попаднали на контролирани компоненти и манипулатори на събития. Трябва да обвържем тези методи с екземпляра на компонента, използвайки .bind()
в конструктора на нашия персонализиран компонент.
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") );
В тази статия ще разберем защо трябва да направим това.
Бих препоръчал да прочетете .bind()
тук, ако все още не знаете какво прави.
Обвинявайте JavaScript, не реагирайте
Е, обвиняването звучи малко грубо. Това не е нещо, което трябва да направим поради начина на работа на React или заради JSX. Това се дължи на начина, по който this
свързването работи в JavaScript.
Нека видим какво ще се случи, ако не обвържем метода на манипулатора на събития с неговия екземпляр на компонент:
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") );
Ако стартирате този код, кликнете върху бутона „Щракнете върху мен“ и проверете вашата конзола. Ще видите undefined
отпечатано на конзолата като стойност от this
от метода за обработка на събития. В handleClick()
метода изглежда са загубили своя контекст (компонент например) или this
стойност.
Как работи това обвързване в JavaScript
Както споменах, това се случва поради начина, по който this
свързването работи в JavaScript. Няма да навлизам в много подробности в тази публикация, но тук има чудесен ресурс, за да разберете как this
свързването работи в JavaScript.
Но от значение за нашата дискусия тук, стойността на this
вътрешната функция зависи от начина на извикване на тази функция.
Обвързване по подразбиране
function display(){ console.log(this); // 'this' will point to the global object } display();
Това е обикновен функционен разговор. Стойността на this
вътре в display()
метода в този случай е прозорецът - или глобалният - обект в не-строг режим. В строг режим this
стойността е undefined
.
Неявно обвързване
var obj = { name: 'Saurabh', display: function(){ console.log(this.name); // 'this' points to obj } }; obj.display(); // Saurabh
Когато извикаме функция по този начин - предшествана от контекстен обект - this
стойността вътре display()
е зададена на obj
.
Но когато присвоим тази препратка към друга променлива и извикаме функцията, използвайки тази нова препратка към функцията, получаваме различна стойност this
отвътре display()
.
var name = "uh oh! global"; var outerDisplay = obj.display; outerDisplay(); // uh oh! global
В горния пример, когато се обаждаме outerDisplay()
, не посочваме контекстен обект. Това е обикновено извикване на функция без обект собственик. В този случай стойността this
отвътре display()
се връща към свързването по подразбиране . Той сочи към глобалния обект или undefined
ако функцията, която се извиква, използва строг режим.
Това е особено приложимо при предаване на такива функции като обратно извикване на друга персонализирана функция, функция на библиотека на трета страна или вградена JavaScript функция като setTimeout
.
Помислете за setTimeout
фиктивната дефиниция, както е показано по-долу, и след това я извикайте.
// A dummy implementation of setTimeout function setTimeout(callback, delay){ //wait for 'delay' milliseconds callback(); } setTimeout( obj.display, 1000 );
Можем да разберем, че когато се обаждаме setTimeout
, JavaScript вътрешно присвоява obj.display
своя аргумент callback
.
callback = obj.display;
Тази операция за присвояване, както видяхме по-рано, кара display()
функцията да загуби своя контекст. Когато този обратен извикване в крайна сметка се извика вътре setTimeout
, this
стойността вътре display()
се връща към обвързването по подразбиране .
var name = "uh oh! global"; setTimeout( obj.display, 1000 ); // uh oh! global
Изрично твърдо подвързване
За да избегнете това, което можем изрично трудно се свързват на this
стойност към функция, с помощта на bind()
метода.
var name = "uh oh! global"; obj.display = obj.display.bind(obj); var outerDisplay = obj.display; outerDisplay(); // Saurabh
Сега, когато се обаждаме outerDisplay()
, стойността на this
точките obj
отвътре display()
.
Дори и да преминем obj.display
като обратно повикване, this
стойността вътре display()
правилно ще сочи obj
.
Пресъздаване на сценария, използвайки само JavaScript
В началото на тази статия видяхме това в нашия React компонент, наречен Foo
. Ако не сме обвързали манипулатора на събития с this
, стойността му вътре в манипулатора на събития е зададена като undefined
.
Както споменах и обясних, това се дължи на начина, по който this
свързването работи в JavaScript и не е свързано с това как работи React. Така че нека да премахнем специфичния за React код и да изградим подобен чист пример на JavaScript, за да симулираме това поведение.
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
Ние не симулираме действителни събития и манипулатори, а вместо това използваме синонимния код. Както забелязахме в примера на React Component, this
стойността беше, undefined
тъй като контекстът беше изгубен след предаване на манипулатора като обратно извикване - синоним на операция за присвояване. Това наблюдаваме и тук, в този фрагмент на JavaScript, който не е React.
„Чакай малко! Не трябва ли this
стойността да сочи към глобалния обект, тъй като изпълняваме това в не-строг режим съгласно правилата на обвързването по подразбиране? " може да попитате.
Не. Ето защо:
Телата на декларациите на класове и класовите изрази се изпълняват в строг режим, т.е. методите на конструктора, статиката и прототипа. Функциите getter и setter се изпълняват в строг режим.Можете да прочетете цялата статия тук.
Така че, за да предотвратим грешката, трябва да свържем this
стойността по следния начин:
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
Не е нужно да правим това в конструктора и можем да направим това и някъде другаде. Помислете за това:
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.