setState Might Be Synchronous

October 22, 2017 by Sylvenas

之前一直知道React中setState是一个异步的操作,如果我们再一个函数内有两个setState,那么他们会合并,只执行最后一个setState,例如:

handleClick = () => {
    console.log(this.state.count); // assume it is 0
    this.setState({ count: this.state.count + 1 });
    this.setState({ count: this.state.count + 1 });
}

两个setState都是异步的,第一个setState不会修改this.state.count,然后我们得到的结果是this.state.count === 1.Right?

但是有一种情况,当setState在React生命周期之外被调用的时候,例如:JavaScript原声的DOM listener(通过addEventListener添加)、setTimeout、setInterval、requestAnimationFrame以及ajax callbacks.这是因为React大部分时间都处于闲置状态,当调用setState的时候,React首先会检查是否有批量更新更在进行,如果有那就添加到批量更新中,如果没有,React就会继续执行更新.

看一下下面的这个例子,当我们点击页面的时候,我们最直接的想法是,state将会从0变成1,但是实际上,它是从0变成了2.

import React from "react";
import { render } from "react-dom";

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

  handleClick = () => {
    console.log(this.state.count);
    this.setState({ count: this.state.count + 1 });
    this.setState({ count: this.state.count + 1 });
    console.log(this.state.count);
  };

  componentDidMount() {
    document.documentElement.addEventListener("click", this.handleClick);
  }

  componentWillUnmount() {
    document.documentElement.removeEventListener("click", this.handleClick);
  }

  render() {
    return (
      <div>
        Click on anywhere. The current state is {this.state.count}{" "}
      </div>
    );
  }
}

render(<App />, document.getElementById("root"));

在大部分情况下,同步的setState也不是什么坏事,很多的初学者对异步的setState感到非常的困惑,不过如果你想在React生命周期之外也能够合并setState,可以使用react-dom中一个隐藏的API,unstable_batchedUpdates,这个api又一个unstable(不稳定)的前缀,不过现在还是可以使用的,以后在升级的时候,注意这个问题就好了。

import { unstable_batchedUpdates} from 'react-dom'

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