在react中,setState是用以改变class组件状态的函数,它有两种用法:
一 传入一个updater函数,该函数有两个参数,一个是当前的state,还有一个是当前的props。该函数的返回值需要是一个更改的state值的对象,它将于state进行浅合并,其用法如下:

    this.setState((state, props) => {
        return { count: state.count + props.number };
    });

二 直接传入一个对象:

    this.setState({ count: this.state.count + this.props.number });

setState函数还可以接受第二个参数,该参数为一个函数,将在更改的State生效之后调用:

    console.log(this.state.count); // 1
    this.setState({ count: 0 }, () => {
        console.log(this.state.count); // 0
    });
    console.log(this.state.count); // ? 此处即可能是1,也可能是0

从上面代码可以看到,最后一行输出的count是不固定的,这是为什么呢?
因为在react中,class内的事件处理程序会默认进行批处理,即如果你在componentDidMount里面调用三次setState函数,那么它最终会在componentDidMount执行完毕后,将三个State的更改合并为一次调用。所以这时候setState就是异步的。
而在其他场景下,setState将会是同步的,例如setTimeout内, Promise的then里面。
一个简单的例子:

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

    this.onClick = this.onClick.bind(this);
  }

  componentDidMount() {
    console.log('componentDidMount before', this.state.count);
    this.setState({ count: this.state.count + 1 });
    console.log('componentDidMount after', this.state.count);
  }

  onClick() {
    console.log('onClick before', this.state.count)
    this.setState({ count: this.state.count + 1 }, () => {
      console.log('setState callback', this.state.count);
    });
    console.log('onClick after', this.state.count);
    Promise.resolve().then(() => {
      console.log('promise.then before', this.state.count);
      this.setState({ count: this.state.count + 1 });
      console.log('promise.then after', this.state.count);
      this.onClassEvent();
    });
  }

  onClassEvent() {
    console.log('onClassEvent before', this.state.count);
    this.setState({ count: this.state.count + 1 });
    console.log('onClassEvent after', this.state.count);
  }

  render() {
    return <div className="test">
      <div>count: {this.state.count}</div>
      <button onClick={this.onClick}>点击改变count</button>
    </div>;
  }
}

让我们运行结果:

clipboard.png

首先第一第二行输出是在componentDidMount里面,我们在函数内调用了setState,并在前后分别输出了改变的值,结果表明,函数调用前与函数调用后该值并没有立即改变,则表明在这里setState是一个异步调用。那么初步判定在生命周期函数内部,setState是异步的调用。

然后第三第四行输出是在onClick函数的回调里面,该函数定义在class中,通过用户点击触发。在setState调用前后我们的输出结果是一致的,这也表明其是一个异步调用。而在setState第二个参数中我们输出了改变后的count, 即第五行输出,表明我们的更改生效了。

然后第六行以后的输出是我们在onClick函数内调用了promise.resolve().then()输出的,它是一个异步调用,react是无法知道它什么时候执行,什么时候完成执行的,所以这时候react默认setState是同步的。从输出我们可以看到每次更改之后,state的值都是立即变化生效的。

而在promise的回调内,我们还调用了一个定义于class内的事件函数,但是该事件函数内的setState也是同步的形式。这说明了setState的同步或者异步与其定义位置并没有直接的关系,而应该取决于是否由React直接进行调用,因为只有是React直接调用的情况下,它才知道该函数什么时候执行完毕,才能进行批处理的优化。否则则默认是同步的调用。(具体内部实现就不展开了,因为我也不是特别懂HHHH,反正意思就大概是这么个意思)

所以当在一些回调内部调用setState时应该注意将多个setState合并,因为它是同步的,多次更新状态会很影响性能。
以及需要注意进行异步调用的时候,如果需要使用变化后的值,请确保在异步调用完成后,一般是在setState的回调内,或者在componentDidUpdate钩子内,但是请注意小心使用,因为很容易一不小心导致循环调用而崩溃。

如果你想在本应同步调用的回调内,对setState进行异步调用,即让它进行批处理,React也提供了一个API:

promise.then(() => {
  // Forces batching
  ReactDOM.unstable_batchedUpdates(() => {
    this.setState({a: true}); // Doesn't re-render yet
    this.setState({b: true}); // Doesn't re-render yet
    this.props.setParentState(); // Doesn't re-render yet
  });
  // When we exit unstable_batchedUpdates, re-renders once
});

在unstable_batchedUpdates内部进行的setState会是异步调用,但是该API是不稳定的,因为后续的React版本更新中将会默认进行批处理即异步调用,届时该API将被删除。而这个后续的版本,很可能就是React 17

记录与分享,欢迎斧正,虚心求教

参考连接:
https://stackoverflow.com/que...
https://react.docschina.org/d...
https://github.com/Advanced-F...
https://github.com/sisterAn/b...


Maosheng
263 声望6 粉丝