От автора: во время работы c React вы должны столкнуться с контролируемыми компонентами и обработчиками событий. Нам нужно связать эти методы с экземпляром компонента, используя React bind () в конструкторе пользовательского компонента.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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") ); |
В этой статье мы узнаем, зачем нам это нужно.
Всему виной JavaScript, не React
Ну, виновен звучит немного сурово. Не нужно никого обвинять из-за того, как работает React или из-за JSX. Это связано с тем, как this привязка работает в JavaScript.
Посмотрим, что произойдет, если мы не свяжем метод обработчика событий с его экземпляром компонента:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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 изнутри метода обработчика событий. Метод handleClick() похоже, потерял свой контекст (экземпляр компонента) или this значение.
Как привязка this работает в JavaScript
Как я уже говорил, это происходит из-за того, как this привязка работает в JavaScript. Я не буду вдаваться в подробности в этом посте, но вот отличный ресурс, чтобы понять, как this связывание работает в JavaScript.
Но, что важно для нашего обсуждения здесь, значение this внутри функции зависит от того, как эта функция вызывается.
Привязка по умолчанию
1 2 3 4 5 |
function display(){ console.log(this); // 'this' will point to the global object } display(); |
Это простой вызов функции. Значение this внутри метода display() в этом случае — это окно — или глобальный — объект в нестрогом режиме. В строгом режиме this значение не undefined.
Неявное связывание
1 2 3 4 5 6 7 8 |
var obj = { name: 'Saurabh', display: function(){ console.log(this.name); // 'this' points to obj } }; obj.display(); // Saurabh |
Когда мы вызываем функцию таким образом — перед объектом контекста — this значение внутри display() устанавливается равным obj.
Но когда мы привязываем эту ссылку к какой-либо другой переменной и вызываем функцию, используя эту новую ссылку на функцию, мы получаем другое значение this внутреннего display().
1 2 3 |
var name = "uh oh! global"; var outerDisplay = obj.display; outerDisplay(); // uh oh! global |
В приведенном выше примере, когда мы вызываем outerDisplay(), мы не указываем объект контекста. Это простой вызов функции без объекта владельца. В этом случае значение this внутреннего display() возвращается к привязке по умолчанию. Он указывает на глобальный объект или undefined если вызываемая функция использует строгий режим.
Это особенно применимо при передаче таких функций, как колбеки другой пользовательской функции, сторонняя библиотечная функция или встроенная функция JavaScript, такая как setTimeout.
Рассмотрим определение типа setTimeout как показано ниже, а затем вызовем его.
1 2 3 4 5 6 7 8 9 |
// A dummy implementation of setTimeout function setTimeout(callback, delay){ //wait for 'delay' milliseconds callback(); } setTimeout( obj.display, 1000 ); |
Мы можем понять, что, когда мы вызываем setTimeout, JavaScript внутренне присваивает obj.display его callback аргумента.
1 |
callback = obj.display; |
Эта операция присваивания, как мы видели ранее, заставляет функцию display() потерять свой контекст. Когда этот callback в конечном итоге вызывается внутри setTimeout, this значение внутри display() возвращается к привязке по умолчанию.
1 2 3 4 |
var name = "uh oh! global"; setTimeout( obj.display, 1000 ); // uh oh! global |
Явное жесткое связывание
Чтобы этого избежать, мы можем явно привязать this значение к функции с помощью метода bind().
1 2 3 4 5 6 |
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, и построим аналогичный пример для Java, чтобы имитировать это поведение.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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 без повторения.
«Подожди минуту! Разве this значение не указывает на глобальный объект, так как мы запускаем его в нестрогом режиме в соответствии с правилами привязки по умолчанию?», вы можете спросить.
Нет. Вот почему:
Тела деклараций классов и выражений классов выполняются в строгом режиме, то есть конструктор, статические и прототипные методы. Функции Getter и setter выполняются в строгом режиме.
Таким образом, чтобы предотвратить ошибку, нам нужно связать this значение следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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 |
Нам не нужно делать это в конструкторе, и мы можем сделать это и в другом месте. Рассмотрим код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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.
Синтаксис Public Class Fields (экспериментальный)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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") ); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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 связано лексически. Это означает, что для этого значения используется контекст охватывающей функции — или глобальной — области.
В случае примера синтаксиса Public Class Fields стрелочная функция заключена внутри функции класса Foo или конструктора, поэтому контекст является экземпляром компонента, который мы хотим.
В случае стрелочной функции в качестве примера обратного вызова стрелочная функция заключена внутри метода render() , который вызывается React в контексте экземпляра компонента. Вот почему стрелочная функция будет также захватывать этот же контекст, и this значение внутри него будет правильно указывать на экземпляр компонента.
Короче говоря
В Class Components в React когда мы передаем ссылку функции обработчика события как обратный вызов, подобный этому
1 |
<button type="button" onClick={this.handleClick}>Click Me</button> |
Метод обработчика событий теряет свой неявно связанный контекст. Когда событие происходит и вызывается обработчик, this значение возвращается к привязке по умолчанию и имеет значение undefined, так как объявления классов и методы прототипа выполняются в строгом режиме.
Когда мы привязываем this обработчик события к экземпляру компонента в конструкторе, мы можем передать его как обратный вызов, не беспокоясь о том, что он потеряет свой контекст.
Стрелочные функции освобождаются от этого поведения, потому что они используют лексическую привязку, которая автоматически привязывает их к области, в которой они определены.
Автор: Saurabh Misra
Источник: //medium.freecodecamp.org/
Редакция: Команда webformyself.