为什么我必须为 React 组件类中定义的方法使用 .bind(this) 而不是在常规 ES6 类中

IT技术 javascript reactjs ecmascript-6
2021-02-18 08:30:36

令我感到困惑的是,为什么当我定义 React 组件类时,this对象中包含的值在类中定义的方法中未定义(this在生命周期方法中可用),除非我使用.bind(this)或使用箭头函数定义方法,例如在下面的代码this.staterenderElements函数中将是未定义的,因为我没有用箭头函数定义它,也没有使用.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)或箭头函数,thisspeak函数中按预期可用

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调用函数,二)为什么生命周期方法(render、componentDidMount 等)已经可以访问该类'this反对,但renderElements没有。

在 React 文档中,它说以下内容

[React Component Class] 方法遵循与常规 ES6 类相同的语义,这意味着它们不会自动将 this 绑定到实例。

但很明显他们这样做了,正如我发布的第二个代码示例所示。

更新

前两条评论中的两个链接都显示了一个未.bind(this)在类方法上使用的 React 类的工作示例,并且工作正常。但仍然在文档中明确表示您需要绑定您的方法,或使用箭头函数。在使用 gulp 和 babel 的项目中,我可以重现。这是否意味着浏览器更新了东西?

更新 2

我的初始代码示例this.renderElements()直接在渲染函数中调用。这将按预期工作,无需绑定函数或使用箭头函数定义它。当我将该函数作为onClick处理程序时会出现问题

更新 3

当我将该函数作为onClick处理程序时会出现问题

事实上,这根本不是问题。this传递给 onClick 处理程序时更改的上下文,这就是 JS 的工作方式。

5个回答

的值this主要取决于函数的调用方式鉴于d.speak();,this将引用,d因为该函数被称为“对象方法”。

但是在<div>{this.renderElements}</div>没有调用该函数时。您正在将函数传递给 React,它将以某种方式调用它。当它被调用时,React 不知道该函数“属于”哪个对象,因此它无法为this. 绑定解决了

其实我觉得你真正想要的是

<div>{this.renderElements()}</div>
//         call function ^^

调用函数作为对象方法。那么你不必绑定它。


查看MDN以了解更多关于this.

组件中的事件处理程序不会像其他方法(生命周期方法...)一样自动绑定到组件实例。

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

您可以查看这篇文章以获取更多信息。

我觉得你是对的。我自己也糊涂了。抱歉提供虚假信息。
2021-04-15 08:30:36
哇,每个人都应该阅读给定的文章。它很好地解释了这一点。这绝对是正确的答案。
2021-04-24 08:30:36
render在未在全局上下文中调用。React 将调用component.render(),因此this将引用component. 还是我误解了你这句话的意思?
2021-04-25 08:30:36
“渲染方法在组件上下文之外被调用,因此渲染方法中的所有 this.functionName 都将引用窗口对象。” 那是不正确的。
2021-05-01 08:30:36
@FelixKling 修复它。我希望现在是正确的。谢谢 !;)
2021-05-08 08:30:36

ES6 类中的函数- @Felix Kling 很好地解释了这种情况。每次在对象上调用函数时,都this指向该对象。

React.Component 中的生命周期方法- 每当 React 实例化您的组件时,就像myComponent = new MyComponent()它知道调用生命周期方法的对象一样,即myComponent所以生命周期方法中myComponent.componentDidUpdate()可以this使用一个简单的调用componentDidUpdate其他生命周期方法相同。

React.Component 中的处理程序和绑定- this.stateis undefined,因为this实际上是window- 记录它并查看。原因是React 在全局上下文中调用处理程序,除非您将处理程序绑定到另一个覆盖的上下文 window(另请参阅 @Phi Nguyen 的回答)。我认为他们这样做是为了让你有更大的灵活性,因为在复杂的应用程序中,你的处理程序可能来自另一个通过 props 传递的组件,然后你想有可能说:“嘿,React -this不是我的组件,但它是家长。”


React 的文档在说的时候是误导性的

方法遵循与常规 ES6 类相同的语义,这意味着它们不会自动将 this 绑定到实例。

他们的意思是

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

1. 箭头函数:

函数表达式相比,箭头函数表达式的语法更短,并且在词法上绑定了 this 值( does not bind its own this, arguments, super, or new.target)。箭头函数总是anonymous. 这些函数表达式最适合非方法函数,它们不能用作构造函数

Function.prototype.bind():

绑定()方法创建一个新的功能,调用它时,具有其将此关键字设置为所提供的值,与前述的当新功能被调用任何设置参数给定的序列。

2.组件规格和生命周期

要绝对明确:大部分生命周期的方法是使用点符号(真不约束,但在一个实例调用componentWillMountcomponentWillUnmountcomponentWillReceiveProps等...),但componentDidMount它因为它被排队到事务绑定到实例。

只要始终将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.