Почему мне нужно .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.