
Содержание статьи
автор Саураб Мисра

Работая над 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 (
<button type="button"
onClick={this.handleClick}>
Click Me
</button>
);
}
}
ReactDOM.render(
<Foo />,
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 (
<button type="button" onClick={this.handleClick}>
Click Me
</button>
);
}
}
ReactDOM.render(
<Foo />,
document.getElementById("app")
);
Если вы запустите этот код, нажмите кнопку Click Me и проверьте свою консоль. Ты увидишь undefined
напечатано на консоли как значение this
изнутри метода обработчика событий. The handleClick()
метод, кажется, есть потеряно его контекст (экземпляр компонента) или this
значение.
Как привязка ‘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, 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
Но конструктор является наиболее оптимальным и эффективным местом для кодирования наших операторов привязки обработчика событий, учитывая, что здесь происходит вся инициализация.
Почему нам не нужно связывать?this’
для стрелочных функций?
У нас есть еще два способа определения обработчиков событий внутри компонента 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")
);
Оба используют функции стрелок, представленные в ES6. При использовании этих альтернатив наш обработчик события уже автоматически привязан к экземпляру компонента и нам не нужно привязывать его в конструкторе.
Причина в том, что в случае стрелковых функций, this
связанный лексически. Это означает, что он использует контекст охватывающей функции или глобальную область как свою this
значение.
В случае синтаксиса полей открытого класса функция стрелки уложена внутри Foo
класс – или функция конструктора – поэтому контекст – это экземпляр компонента, что мы и хотим.
В случае стрелочной функции в качестве примера обратного вызова стрелочная функция заключена внутри render()
метод, который вызывает React в контексте экземпляра компонента. Вот почему функция стрелки также будет фиксировать этот же контекст и this
значение внутри него будет правильно указывать на экземпляр компонента.
Подробнее о лексике this
обязательно, посмотрите этот замечательный ресурс.
Если коротко
В компонентах класса в React, когда мы передаем ссылку на функцию обработчика событий как обратный вызов
<button type="button" onClick={this.handleClick}>Click Me</button>
метод обработчика событий теряет свою неявно связанный контекст. Когда происходит событие и вызывается обработчик, файл this
значение возвращается к связывание по умолчанию и установлено на undefined
поскольку объявления классов и методы прототипов работают в строгом режиме.
Когда мы связываем this
обработчик события в экземпляр компонента в конструкторе, мы можем передать его как обратный вызов, не беспокоясь о том, что он потеряет свой контекст.
Функции со стрелками освобождены от такого поведения, поскольку они используются лексический this
связывание который автоматически привязывает их к области, где они определены.