setState何时同步更新状态

setState何时同步更新状态

上次写了一篇 setState为什么不会同步更新组件状态 - 知乎专栏,有朋友说,其实setState也可以同步更新this.state的,我研究了一下,真的耶!有些情况下setState真的可以同步更新this.state耶!

官方文档对setState这种同步行为语焉不详,所以只能去看源代码,港真,我真的不想去看React的源代码,但是遇到这种事也没有更好的办法,毕竟,开源软件的好处不就是可以去看源代码嘛。

先直接说结论:

在React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.state。所谓“除此之外”,指的是绕过React通过addEventListener直接添加的事件处理函数,还有通过setTimeout/setInterval产生的异步调用。

如果我们按照教科书般的方式来使用React,基本上不会触及所谓的“除此之外”情况。

再说为什么会这样:

在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中回头再说,而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是,有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state。

上面的介绍是不是有点枯燥?我希望不要太枯燥,如果你不关心缘由,那就直接记住结论就行了。

为了展示效果,我们来看一段代码,在 JS Bin 上,一个简单的点击按钮增加计数的功能。

在onClick函数中,我们调用setState函数,然后在console.log上输出this.state,由此判断setState是否同步更新了this.state。

  onClick() {
    this.setState({count: this.state.count + 1});
    console.log('# this.state', this.state);
  }

在render函数中,我们用console.log输出一个信息,通过render函数是否被执行,我们能够判断更新过程是否发生了,然后我们用三个按钮分别代表三种事件处理方式。

  render() {
    console.log('#enter render');
    return (
      <div>
        <div>{this.state.count}
          <button onClick={this.onClick}>Increment</button>
          <button id="btn-raw">Increment Raw</button>
          <button onClick={this.onClickLater}>Increment Later</button>
        </div>
      </div>
    )

  }

最后显示的界面是这样。

Increment按钮使用最正规的onClick方式处理点击事件。

Increment Raw通过addEventListener处理点击事件。

  componentDidMount() {
    document.querySelector('#btn-raw').addEventListener('click', this.onClick);
  }

Increment Later通过setTimeout来处理点击事件。

  onClickLater() {
    setTimeout(() => {
      this.onClick();
    });
  }

通过点击三个不同的按钮,我们可以看到不同的行为。

点击Increment,先输出没有更新的this.state,然后render函数被执行,可见this.state的更新是异步的,更新过程也是在setState执行之后才引发。

但是如果点击Increment Raw或者Increment Later,就是先执行render函数,然后输输出更新过的this.state,可见,this.state被同步更新了,而且在setState函数执行过程中更新过程就已经完成了。

你还希望setState同步更新this.state吗?

上面的试验很清楚地显示,同步更新this.state的话,每一次调用setState都会引发同步的更新过程,这会更新过程很频繁,也就会导致性能问题。

所以说,虽然React具有让setState同步更新this.state的功能,我们还是避免这种使用方式。

别用这招,我们可以了解一种工具,但是并不表示我们就应该使用它。

编辑于 2017-03-29

文章被以下专栏收录