本文基于React17.0.0

setState的执行流程

简略来说,setState函数是React.Component类的一个办法,它记录了以后利用产生的更新update,并把这些update用一个队列updateQueue记录下来,而后进入scheduleUpdateOnFiber这个函数中进行调度更新,而后触发performSyncWorkOnRoot函数来进入render阶段。最初在commit阶段把更新渲染到dom上。

同步还是异步

为了更加具体的了解setState的执行行为,咱们先来看上面的代码,预设咱们是在ReactDOM.renderlegacy模式下运行,你能够正确说出两次console.log的后果吗?

constructor(props) {    super(props);    this.state = {      data: "hello"    }  }componentDidMount() {    //第一个setState    this.setState({      data: 'world'    })        console.log("in componentDidMount: ", this.state.data);    setTimeout(() => {      // 第二个setState      this.setState({        data: 'Bob'      })      console.log("in setTimeout: ", this.state.data);    })  }

揭晓答案,后果是

咱们能够看到,第一个log输入的是hello,也就是说,当执行第一个setState时,是体现为同步行为的,在执行第二个setState时,log输入的是Bob,是体现为异步行为的。

那么这是为什么呢,能够粗略看出,两个setState执行的环境是不同的,第一个setState的执行环境是在componentDidMount()这个生命周期钩子函数中,第二个setState是在setTimeout中执行的。置信大家一眼就看进去了,问题的实质就是执行环境的不同,导致了setState在执行时的上下文就不一样了,而执行上下文executionContext,就是决定setState同步异步行为的要害。

为何executionContext为何在这个过程中这么重要呢,咱们在后面理解到setState在执行过程中会进入 scheduleUpdateOnFiber这个函数,咱们先来简略看下在React源码中,有什么蹊跷吧。

scheduleUpdateOnFiber

function scheduleUpdateOnFiber(fiber, lane, eventTime) {  // 省略 .....    if (lane === SyncLane) {// ReactDOM.render 同步执行    if ( // Check if we're inside unbatchedUpdates    (executionContext & LegacyUnbatchedContext) !== NoContext && // Check if we're not already rendering    (executionContext & (RenderContext | CommitContext)) === NoContext) {      // Register pending interactions on the root to avoid losing traced interaction data.      schedulePendingInteractions(root, lane); // This is a legacy edge case. The initial mount of a ReactDOM.render-ed      performSyncWorkOnRoot(root);    } else {      ensureRootIsScheduled(root, eventTime);      schedulePendingInteractions(root, lane);      if (executionContext === NoContext) {        resetRenderTimer();        flushSyncCallbackQueue();      }    }  } else {    // 省略 .....   }     ensureRootIsScheduled(root, eventTime);    schedulePendingInteractions(root, lane);  }   mostRecentlyUpdatedRoot = root;} 

咱们能够看到其中的要害代码

if (executionContext === NoContext) {        resetRenderTimer();        flushSyncCallbackQueue();}

其中flushSyncCallbackQueue函数的作用就是同步执行更新队列里的更新update,后面咱们说过了setState后会把更新对象update以队列模式保留下来,这里就是要执行这些更新并清空队列。
咱们能够看出,如果以后的执行上下文executionContextNoContext,也就是说明React曾经不处于本人的调度环节了,而是处于一种无事可做的状态时,React就会去同步的执行setState的回调函数进行更新。
那么,什么时候才会是一种无事可做的状态呢?

答案就是不处于React自身的调度阶段时,比方setTimeout,网络申请,间接在Dom节点上绑定的事件等,这些行为都不会触发React的调度行为。当React处于本人的调度阶段时,会依据所处的状态不同给executionContext赋值不同的值,如赋值为BatchedContext时阐明进入了更新合并阶段,而executionContext默认状况下就是NoContext。所以不在调度阶段时,React就会进入无事可做的状态,就会将setState同步执行。在React处于本人的调度阶段,会执行诸如生命周期钩子函数,合成事件等,在这些状况下,会触发batchedUpdate进行合并更新,所以此时将executionContext赋值为BatchedContext,那么天然就是异步的行为了。

在咱们的例子中,componentDidMount属于React调度流程的一部分,所以其中的setState会被异步执行,componentDidMount执行完后,React就退出调度过程了,此时的executionContextNoContext 了,然而咱们的代码还没有执行完,因为setTimeout是一个异步办法,等它的回调被执行的时候,React就会以同步的形式执行setState了。

setState与useState

useState是React为函数组件赋予状态的一种做法,然而它和setState的调用栈有重合的中央,也就是都会进入scheduleUpdateOnFiber函数,所以在同步异步的行为上是没有区别的。