React 中setState更新state什么时候同步什么时候异步?

先说结论

  • React控制的事件处理程序,以及生命周期内调用setState是异步更新state
  • React控制以外的事件中调用setState是同步更新state,好比原生js绑定事件、setTimeout/setInrerval等。

setState的“异步”并非说内部由异步代码实现,自己的执行过程和代码都是同步的。算法

之因此会有一种异步方法的表现形式,归根结底仍是由于React框架自己的性能机制所致使的。由于每次调用setState都会触发更新,异步操做是为了提升性能,将多个状态合并一块儿更新,减小re-render调用。性能优化

咱们来看一些例子:框架

React封装事件

state = {
  number: 1
}

componentDidMount() {
  this.setState({ number: 3 });
  console.log(this.state.number); // 1
}

因而可知该事件处理程序中的setState是异步更新state的。异步

setTimeout

state = {
  number: 1
}

componentDidMount() {
  setTimeout(() => {
    this.setState({ number: 3 });
  }, 0);
  console.log(this.state.number); // 3
}

上面咱们讲到,setState自己并非一个异步方法,之因此会变现出一种异步的形式,是由于React框架自己的一种性能优化机制。那么基于这一点,假如咱们能绕过React的机制,就能够令setState以同步的形式体现。函数

原生事件

state = {
  number: 1
}

componentDidMount() {
  document.body.addEventListener('click', this.resetState, false);
}

resetState() {
  this.setState({ number: 3 });
  console.log(this.state.number); // 3
}

一样的,原生事件也能够绕过React的性能优化机制,达到同步更新的表现。性能

React是如何控制异步和同步的?

ReactsetState函数实现中,会根据一个变量isBatchingUpdates判断是否直接更新this.state,仍是放入队列中延时更新。优化

isBatchingUpdates默认是false,标识setState是同步更新this.state。可是有一个函数batchedUpdates会把isBatchingUpdates修改成true,而当React在调用事件处理函数以前就会先调用这个函数将isBatchingUpdates修改成true。这样由React控制的事件处理过程setState就不会同步更新this.statethis

function enqueueUpdate(component){
  //injected注入的
  ensureInjected();
  //若是不处于批量更新模式
  if(!batchingStrategy.isBatchingUpdates){
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }
  //若是处于批量更新模式
  dirtyComponents.push(component);
}

image

事实上setState内部的执行过程是很复杂的,大体过程包括更新state,建立新的VNode,再通过diff算法对比差别,决定渲染哪一部分以及怎么渲染,最终造成最新的UI。这一过程包含组件的四个生命周期函数:spa

  • shouComponentUpdate
  • componentWillUpdate
  • render
  • componentDidUpdate

若是是子组件而且依赖父组件,还会执行一个钩子函数componentWillReceivePropscode

假如setState是同步更新的,每次更新这个过程都要完整执行一次,无疑会形成性能问题。事实上这些生命周期为纯函数,对性能还好,可是diff比较、更新DOM总消耗时间和性能吧。

在“异步”中若是对同一个值进行屡次setStatesetState 的批量更新策略会对其进行覆盖,取最后一次的执行。

state = {
  number: 1
}

handleClick() {
  const number = this.state.number;

  this.setState({ number: number + 1 });
  this.setState({ number: number + 1 });
  this.setState({ number: number + 1 });
}

最终的结果只加了1。

若是是同时 setState 多个不一样的值,在更新时会对其进行合并批量更新。

hanldeClick() {
  this.setState({ name: 'Clearlove' });
  this.setState({ age: 18 });
}

hanldeClick处理程序中调用了两次setState,可是render只执行了一次。由于React会将多个this.setState产生的修改放在一个队列里进行批延时处理。

如何获取“异步”更新后的数据?

setState提供了一个回调函数供开发者使用,在回调函数中,咱们能够实时的获取到更新以后的数据。仍是以刚才的例子作示范:

state = {
  number: 1
}

componentDidMount() {
  this.setState({ number: 3 }, () => {
    console.log(this.state.number); // 3
  });
}
相关文章
相关标签/搜索