Почему мне нужно .bind(this) для методов, определенных в классе компонентов React, но не в обычном классе ES6
Что-то меня озадачивает, поэтому, когда я определяю класс реагирующих компонентов, значения, содержащиеся в объекте this
, являются undefined в определенных методах (this
доступен в методах жизненного цикла) в классе, если я не использую .bind(this)
или определить метод, используя функцию стрелки, например, в следующем коде this.state
будет undefined в функции renderElements
, потому что я не определял его со стрелкой и не использовал .bind(this)
class MyComponent extends React.Component {
constructor() {
super();
this.state = { elements: 5 }
}
renderElements() {
const output = [];
// In the following this.state.elements will be undefined
// because I have not used .bind(this) on this method in the constructor
// example: this.renderElements = this.renderElements.bind(this)
for(let i = 0; i < this.state.elements; i ++){
output.push(<div key={i} />);
}
return output;
}
// .this is defined inside of the lifecycle methods and
// therefore do not need call .bind(this) on the render method.
render() {
return (
<div onClick={this.renderElements}></div>
);
}
}
Тогда в следующем примере мне не нужно использовать .bind(this)
или функцию стрелки, this
доступен, как ожидалось, в speak
function
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
class Dog extends Animal {
speak() {
console.log(this.name + ' barks.');
}
}
var d = new Dog('Mitzie');
d.speak();
http://jsbin.com/cadoduxuye/edit?js,console
Чтобы уточнить, мой вопрос состоит из двух частей. Один), почему во втором примере кода мне не нужно вызывать .bind(this)
для функции speak
, но я делаю в компоненте React для функции renderElements
и Two), почему методы жизненного цикла (render, componentDidMount, и т.д.) уже имеют доступ к объекту класса this
, но renderElements
нет.
В документах React говорится следующее
[React Component Class] Методы следуют той же семантике, что и обычные классы ES6, что означает, что они автоматически не связывают это с экземпляром.
Но ясно, что они делают, как показывает второй пример кода, который я опубликовал.
Обновление
Обе ссылки в первых двух комментариях показывают рабочий пример классов React, не использующих .bind(this)
для методов класса, и он отлично работает. Но все же в документах явно указано, что вам нужно привязать свои методы или использовать функцию стрелки. В проекте с использованием gulp и babel я могу воспроизвести. Может ли это означать, что браузеры обновили?
Обновление 2
В моем первоначальном примере кода был this.renderElements()
, вызываемый непосредственно в функции рендеринга. Это будет работать, как ожидалось, без привязки функции или определения ее со стрелкой. Проблема возникает, когда я помещаю функцию в качестве обработчика onClick
.
Обновление 3
Проблема возникает, когда я помещаю функцию в качестве обработчика onClick
.
На самом деле это не проблема. Контекст this
изменяется при передаче обработчику onClick, поэтому как работает JS.
Ответы
Ответ 1
Обработчики событий в компоненте не будут привязаны автоматически к экземпляру компонента, как другие методы (методы жизненного цикла...).
class MyComponent extends React.Component {
render(){
return (
<div onClick={this.renderElements}>
{this.renderElements()} <-- `this` is still in side the MyComponent context
</div>
)
}
}
//under the hood
var instance = new MyComponent();
var element = instance.render();
//click on div
element.onClick() <-- `this` inside renderElements refers to the window object now
Проверьте этот пример, чтобы понять this
больше:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
class Dog extends Animal {
run(){
console.log(this.name + ' runs');
}
speak() {
console.log(this.name + ' barks.');
this.run(); <-- `this` is still in the Dog context
return {onRun : this.run};
}
}
var d = new Dog('Mitzie');
var myDog = d.speak();
myDog.onRun() <-- `this` is now in the global context which is the `window` object
Подробнее об этом можно узнать в статье.
Ответ 2
Значение this
в первую очередь зависит от того, как вызывается функция. Учитывая d.speak();
, this
будет ссылаться на d
, потому что функция вызывается как "метод объекта".
Но в <div>{this.renderElements}</div>
вы не вызываете функцию. Вы передаете функцию React, которая вызовет ее как-то. Когда он вызывается, React не знает, к какому объекту относится функция "принадлежит", поэтому он не может установить правильное значение для this
. Связывание решает, что
Я действительно думаю, что вы действительно хотите
<div>{this.renderElements()}</div>
// call function ^^
Я вызываю функцию как метод объекта. Тогда вам не нужно связывать его.
Посмотрите MDN, чтобы узнать больше о this
.
Ответ 3
Функции в классах ES6 - случай очень хорошо объясняется @Felix Kling. Каждый раз, когда вы вызываете функцию объекта, this
указывает на объект.
Способы жизненного цикла в React.Component - всякий раз, когда React создает экземпляр вашего компонента, например myComponent = new MyComponent()
, он знает, на какой объект вызывать методы жизненного цикла, а именно myComponent. Таким образом, простой вызов myComponent.componentDidUpdate()
делает this
доступным в методе жизненного цикла componentDidUpdate
. То же самое для других методов жизненного цикла.
Обработчики и привязанные в React.Component - this.state
являются undefined
, потому что this
на самом деле window
- запишите его и посмотрите. Причина в том, что React вызывает обработчики в глобальном контексте, если только у вас нет обработчика, связанного с другим контекстом, который переопределяет window
(см. Также ответ @Phi Nguyen).
Я думаю, что они сделали это, чтобы обеспечить вам большую гибкость, потому что в сложных приложениях ваш обработчик может поступать из другого компонента, переданного через реквизиты, и тогда вы хотели бы иметь возможность сказать: "Эй, Реакт - this
не мой компонент, но он является родителем.
Реагирующая документация - это ввод в заблуждение, когда он говорит
Методы следуют той же семантике, что и обычные классы ES6, что означает, что они автоматически не связывают это с экземпляром.
Что они означают, так это то, что
var dog = new Dog('Mitzie');
speak = d.speak;
dog.speak() // this will be dog, because the function is called on dog
speak() // this will be window, and not dog, because the function is not bound
Ответ 4
1.
Функции стрелок:
выражение функции имеет более короткий синтаксис по сравнению с выражением функции, а лексически связывает это значение (does not bind its own this, arguments, super, or new.target
). Функции стрелок всегда anonymous
. Эти функциональные выражения лучше всего подходят для не-методов, и они не могут использоваться как конструкторы.
Function.prototype.bind():
Метод bind() создает новую функцию, которая при вызове имеет это ключевое слово, установленное на предоставленное значение, с заданной последовательностью аргументов, предшествующей любому, когда она вызывается при вызове новой функции.
2. Компонентные характеристики и жизненный цикл
Чтобы быть абсолютно ясным: большинство методов жизненного цикла не связаны, но вызываются экземпляром с использованием точечной нотации (true для componentWillMount
, componentWillUnmount
, componentWillReceiveProps
и т.д.), за исключением componentDidMount
который привязан к экземпляру, так как он попадает в транзакцию.
Ответ 5
Просто всегда добавляйте код autoBind(this);
в свой конструктор и не беспокойтесь о указателях методов.
npm install --save auto-bind-inheritance
const autoBind = require('auto-bind-inheritance');
class Animal {
constructor(name) {
autoBind(this);
this.name = name;
}
printName() { console.log(this.name); }
...
}
let m = new Animal('dog');
let mpntr = m.printName;
m.printName() //> 'dog'
mpntr() //> 'dog', because auto-bind, binds 'this' to the method.