我正在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

当前回答

这一点。在作为onChange道具传递之前,changeContent需要通过this.changeContent.bind(this)绑定到组件实例,否则函数体中的this变量将不会指向组件实例,而是指向窗口。看到函数::绑定。

当使用React时。createClass而不是ES6类,组件上定义的每个非生命周期方法都会自动绑定到组件实例。看到Autobinding。

注意绑定一个函数会创建一个新函数。您可以直接在呈现中绑定它,这意味着每次组件呈现时都会创建一个新函数,也可以在构造函数中绑定它,这只会触发一次。

constructor() {
  this.changeContent = this.changeContent.bind(this);
}

vs

render() {
  return <input onChange={this.changeContent.bind(this)} />;
}

引用设置在组件实例上,而不是在React上。refs:你需要将react .ref .someref改为this.ref .someref。您还需要将sendContent方法绑定到组件实例,以便该方法引用它。

其他回答

您的函数需要绑定,以便使用事件处理程序中的状态或道具

在ES5中,只在构造函数中绑定事件处理函数,而不直接在呈现中绑定。如果你直接在渲染中绑定,那么每次你的组件渲染和重新渲染时,它都会创建一个新函数。所以你应该总是在构造函数中绑定它

this.sendContent = this.sendContent.bind(this)

在ES6中,使用箭头函数

当你使用箭头函数时,你不需要做绑定,你也可以远离范围相关的问题

sendContent = (event) => {

}

Morhaus是正确的,但这个问题可以不受约束地解决。

你可以将箭头函数与类属性建议一起使用:

class SomeClass extends React.Component {
  changeContent = (e) => {
    this.setState({inputContent: e.target.value})
  } 

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

因为箭头函数是在构造函数的作用域中声明的,而且因为箭头函数从它们的声明作用域中维护这一点,所以一切都可以正常工作。这里的缺点是这些不会是原型上的函数,它们都将与每个组件一起重新创建。然而,这并不是什么缺点,因为绑定也会产生同样的结果。

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

这是动态的

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

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.

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>
    );
  }
}

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

你可以用三种方法来解决这个问题

1.在构造函数本身中绑定事件函数,如下所示

import React from 'react'

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

  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

2.在调用时绑定

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.bind(this)}>Submit</button>
      </div>
    )
  }
}

export default SomeClass

3.通过使用箭头函数

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