如何修复React类组件中未定义的 "this.state"?

259 阅读2分钟

如果你正在使用React类组件,你可能会发现自己被'this.state'所困扰,undefined

问题所在

你可能正试图在事件处理程序中调用一个实例方法。考虑下面的例子,一个基本的计数器,我们可以增减。

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      count: 0,
    };
  }

  increment() {
    this.setState({ count: this.state.count + 1 });
  }

  decrement() {
    this.setState({ count: this.state.count - 1 });
  }

  render() {
    return (
      <div className="App">
        <h1>Counter</h1>
        <div>{this.state.count}</div>
        <button onClick={this.increment}>Increment</button>
        <button onClick={this.decrement}>Decrement</button>
      </div>
    );
  }
}

export default App;

当我们点击增量或减量按钮时,我们会得到一个错误:Cannot read property 'count' of undefined 。我们的控制台会显示,确实看起来this.state 是未定义的。

那么,为什么会出现这种情况呢?

原因是

使用ES6类的React组件不会自动绑定this 到非React.Component方法。这意味着我们无法保证incrementdecrement 方法中的this 是指 React 类组件的实例。

解决方案

解决方案1:在构造函数中绑定

我们有几个不同的选择。最流行的,也是我最喜欢的,是在constructor 方法中立即将我们的方法与React组件实例绑定。

这个实现的结果看起来像。

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      count: 0,
    };
    this.increment = this.increment.bind(this);
    this.decrement = this.decrement.bind(this);
  }

  increment() {
    this.setState({ count: this.state.count + 1 });
  }

  decrement() {
    this.setState({ count: this.state.count - 1 });
  }

  render() {
    return (
      <div className="App">
        <h1>Counter</h1>
        <div>{this.state.count}</div>
        <button onClick={this.increment}>Increment</button>
        <button onClick={this.decrement}>Decrement</button>
      </div>
    );
  }
}

export default App;

解决方案2:在事件处理程序中绑定

由于我们只调用一次this.incrementthis.decrement ,我们实际上可以只在button 事件处理程序本身中传递这些函数的绑定版本。这样做可以,但如果你要在多个地方调用这些方法,很快就会变得效率低下,而且容易出错。

下面是这种方法的样子。

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      count: 0,
    };
  }

  increment() {
    this.setState({ count: this.state.count + 1 });
  }

  decrement() {
    this.setState({ count: this.state.count - 1 });
  }

  render() {
    return (
      <div className="App">
        <h1>Counter</h1>
        <div>{this.state.count}</div>
        <button onClick={this.increment.bind(this)}>Increment</button>
        <button onClick={this.decrement.bind(this)}>Decrement</button>
      </div>
    );
  }
}

export default App;

解决方案3:将方法改为箭头函数类属性

我们的最后一个选择是将我们的incrementdecrement 方法改为箭头函数类属性。这个方案表面上看起来很诱人,但我们在看完实现后会讨论为什么它是我最不喜欢的方案。

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      count: 0,
    };
  }

  increment = () => {
    this.setState({ count: this.state.count + 1 });
  };

  decrement = () => {
    this.setState({ count: this.state.count - 1 });
  };

  render() {
    return (
      <div className="App">
        <h1>Counter</h1>
        <div>{this.state.count}</div>
        <button onClick={this.increment}>Increment</button>
        <button onClick={this.decrement}>Decrement</button>
      </div>
    );
  }
}

export default App;

确实有效,但最大的问题是,每次创建组件的新实例时,都会创建incrementdecrement 。换句话说,它们存在于实例上而不是构造函数上。与存在于原型上的属性相比,这最终在理论上是低效的。

总结

在这篇文章中,我们发现了为什么React类组件可能会出现未定义的状态,以及解决这个问题的几种不同方法。