我正在ES6中编写一个简单的组件(使用BabelJS),并函数此。setState不工作。

典型的错误包括

无法读取undefined的属性“setState”

or

这一点。setState不是一个函数

你知道为什么吗?代码如下:

import React from 'react'

class SomeClass extends React.Component {
  constructor(props) {
    super(props)
    this.state = {inputContent: 'startValue'}
  }

  sendContent(e) {
    console.log('sending input content '+React.findDOMNode(React.refs.someref).value)
  }

  changeContent(e) {
    this.setState({inputContent: e.target.value})
  } 

  render() {
    return (
      <div>
        <h4>The input form is here:</h4>
        Title: 
        <input type="text" ref="someref" value={this.inputContent} 
          onChange={this.changeContent} /> 
        <button onClick={this.sendContent}>Submit</button>
      </div>
    )
  }
}

export default SomeClass

当前回答

此问题发生在react15.0之后,该事件处理程序没有自动绑定到组件。因此,无论何时调用事件处理程序,都必须手动将其绑定到组件。


有几种方法可以解决这个问题。但是你需要知道哪种方法是最好的,为什么?通常,我们建议将函数绑定到类构造函数中,或者使用箭头函数。

// method 1: use a arrow function
    class ComponentA extends React.Component {
      eventHandler = () => {
        console.log(this)
      }
      render() {
        return ( 
        <ChildComponent onClick={this.eventHandler} /> 
        );
      }

// method 2: Bind your functions in the class constructor.
    class ComponentA extends React.Component {
      constructor(props) {
        super(props);
        this.eventHandler = this.eventHandler.bind(this);
      }
      render() {
        return ( 
        <ChildComponent onClick={this.eventHandler} /> 
        );
      }

这两个方法不会在组件每次渲染时创建一个新函数。所以我们的ChildComponent将不会因为新的函数道具的改变而重新渲染,或者可能会产生性能问题。

其他回答

这个问题的发生是因为。changeContent和onClick={this。sendContent}不绑定到组件实例的this。

还有另一种解决方案(除了在构造函数()中使用bind()之外)使用ES6中的箭头函数,这些函数与周围的代码共享相同的词法范围并维护它,所以你可以在render()中更改你的代码为:

render() {
    return (

        <input type="text"
          onChange={ () => this.changeContent() } /> 

        <button onClick={ () => this.sendContent() }>Submit</button>

    )
  }

虽然前面的回答已经提供了解决方案的基本概述(即绑定、箭头函数、为您做这件事的装饰器),但我还没有遇到一个真正解释为什么这是必要的答案——在我看来,这是困惑的根源,并导致不必要的步骤,如不必要的重新绑定和盲目地跟随别人做什么。

这是动态的

要了解这种具体情况,简要介绍一下它是如何工作的。这里的关键是,这是一个运行时绑定,依赖于当前的执行上下文。因此,为什么它通常被称为“上下文”——提供当前执行上下文的信息,以及为什么需要绑定是因为您失去了“上下文”。但让我用一个片段来说明这个问题:

const foobar = {
  bar: function () {
    return this.foo;
  },
  foo: 3,
};
console.log(foobar.bar()); // 3, all is good!

在这个例子中,我们得到3,正如预期的那样。举个例子:

const barFunc = foobar.bar;
console.log(barFunc()); // Uh oh, undefined!

可能会意外地发现它的日志没有定义——3去哪里了?答案在于“上下文”,或者你如何执行一个函数。比较一下我们如何调用函数:

// Example 1
foobar.bar();
// Example 2
const barFunc = foobar.bar;
barFunc();

注意区别。在第一个例子中,我们精确地指定了bar method1在foobar对象上的位置:

foobar.bar();
^^^^^^

但在第二种方法中,我们将方法存储到一个新变量中,并使用该变量来调用该方法,而不显式地声明该方法实际存在的位置,从而丢失上下文:

barFunc(); // Which object is this function coming from?

问题就在这里,当你在变量中存储一个方法时,关于该方法所在位置的原始信息(该方法正在执行的上下文)就丢失了。在运行时,如果没有这些信息,JavaScript解释器就无法绑定正确的this——如果没有特定的上下文,这将无法正常工作2。

关于React

下面是一个React组件(为简洁起见而缩写)遇到这个问题的例子:

handleClick() {
  this.setState(({ clicks }) => ({ // setState is async, use callback to access previous state
    clicks: clicks + 1, // increase by 1
  }));
}

render() {
  return (
    <button onClick={this.handleClick}>{this.state.clicks}</button>
  );
}

但是为什么,前面的部分与此有什么关系呢?这是因为他们对同一个问题的抽象。如果你看一下React如何处理事件处理器:

// Edited to fit answer, React performs other checks internally
// props is the current React component's props, registrationName is the name of the event handle prop, i.e "onClick"
let listener = props[registrationName];
// Later, listener is called

当你onClick={this。handleClick},方法this。handleClick最终被分配给变量listener3。但现在你看到问题了,因为我们分配了这个。handleClick到监听器,我们不再指定handleClick来自哪里!从React的角度来看,监听器只是一个函数,没有附加到任何对象(或者在这种情况下,React组件实例)。我们丢失了上下文,因此解释器无法推断出在handleClick内部使用的this值。

为什么绑定有效

您可能想知道,如果解释器在运行时决定this值,为什么我可以绑定处理程序,以便它工作?这是因为你可以使用Function#bind在运行时保证This值。这是通过在函数上设置一个内部This绑定属性来实现的,允许它不推断这个:

this.handleClick = this.handleClick.bind(this);

当执行这一行时,假设在构造函数中,当前的this被捕获(React组件实例),并设置为一个全新函数的内部this绑定,从function #bind返回。这可以确保在运行时计算这个值时,解释器不会尝试推断任何东西,而是使用您提供的This值。

为什么箭头函数属性有效

箭头函数类属性目前通过Babel基于翻译工作:

handleClick = () => { /* Can use this just fine here */ }

就变成:

constructor() {
  super();
  this.handleClick = () => {}
}

这是因为箭头函数不绑定自己的this,而是取其封闭范围的this。在本例中,构造函数是this,它指向React组件实例——因此给出了正确的this.4


我用“method”来指代应该绑定到对象上的函数,用“function”来指代那些没有绑定到对象上的函数。

2在第二个代码片段中,未定义被记录为日志,而不是3,因为当不能通过特定上下文确定时,默认为全局执行上下文(非严格模式时为窗口,否则为未定义)。在示例窗口中。Foo不存在,因此产生undefined。

如果你深入到事件队列中事件是如何执行的,监听器上调用invokeGuardedCallback。

4 It's actually a lot more complicated. React internally tries to use Function#apply on listeners for its own use, but this does not work arrow functions as they simply do not bind this. That means, when this inside the arrow function is actually evaluated, the this is resolved up each lexical environment of each execution context of the current code of the module. The execution context which finally resolves to have a this binding is the constructor, which has a this pointing to the current React component instance, allowing it to work.

我们必须将我们的函数与此绑定,以获得类中的函数实例。举个例子

<button onClick={this.sendContent.bind(this)}>Submit</button>

这边,这边。状态将是有效对象。

如果有人能找到这个答案, 下面是一种无需手动绑定所有函数的方法

在构造函数()中:

for (let member of Object.getOwnPropertyNames(Object.getPrototypeOf(this))) {
    this[member] = this[member].bind(this)
}

或者在全局变量中创建这个函数。jsx文件

export function bindAllFunctions({ bindTo: dis }) {
for (let member of Object.getOwnPropertyNames(Object.getPrototypeOf(dis))) {
    dis[member] = dis[member].bind(dis)
    }
}

并在你的构造函数()调用它如下:

bindAllFunctions({ bindTo: this })

Bind (this)可以解决这个问题,现在如果你不喜欢使用Bind,我们可以使用另外两种方法来实现这个目标。

1)和传统的方法一样,我们可以在构造函数中使用bind(this),这样当我们使用函数作为JSX回调时,this的上下文就是类本身。

class App1 extends React.Component {
  constructor(props) {
    super(props);
    // If we comment out the following line,
    // we will get run time error said `this` is undefined.
    this.changeColor = this.changeColor.bind(this);
  }

  changeColor(e) {
    e.currentTarget.style.backgroundColor = "#00FF00";
    console.log(this.props);
  }

  render() {
    return (
      <div>
        <button onClick={this.changeColor}> button</button>
      </div>
    );
  }
}

2)如果我们将函数定义为带有箭头函数的类的属性/字段,我们就不需要再使用bind(this)了。

class App2 extends React.Component {
  changeColor = e => {
    e.currentTarget.style.backgroundColor = "#00FF00";
    console.log(this.props);
  };
  render() {
    return (
      <div>
        <button onClick={this.changeColor}> button 1</button>
      </div>
    );
  }
}

3)如果我们使用箭头函数作为JSX回调,我们也不需要使用bind(this)。更进一步,我们可以传递参数。看起来不错,不是吗?但它的缺点是性能问题,详情请参阅ReactJS doco。

class App3 extends React.Component {
  changeColor(e, colorHex) {
    e.currentTarget.style.backgroundColor = colorHex;
    console.log(this.props);
  }
  render() {
    return (
      <div>
        <button onClick={e => this.changeColor(e, "#ff0000")}> button 1</button>
      </div>
    );
  }
}

我已经创建了一个代码依赖来演示这些代码片段,希望它能有所帮助。