本文基于React17.0.0
setState的执行流程
简略来说,setState
函数是React.Component
类的一个办法,它记录了以后利用产生的更新update
,并把这些update
用一个队列updateQueue
记录下来,而后进入scheduleUpdateOnFiber
这个函数中进行调度更新,而后触发performSyncWorkOnRoot
函数来进入render
阶段。最初在commit
阶段把更新渲染到dom上。
同步还是异步
为了更加具体的了解setState
的执行行为,咱们先来看上面的代码,预设咱们是在ReactDOM.render
的legacy
模式下运行,你能够正确说出两次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
以队列模式保留下来,这里就是要执行这些更新并清空队列。
咱们能够看出,如果以后的执行上下文executionContext
是NoContext
,也就是说明React
曾经不处于本人的调度环节了,而是处于一种无事可做的状态时,React
就会去同步的执行setState
的回调函数进行更新。
那么,什么时候才会是一种无事可做的状态呢?
答案就是不处于React自身的调度阶段时,比方setTimeout
,网络申请,间接在Dom
节点上绑定的事件等,这些行为都不会触发React的调度行为。当React处于本人的调度阶段时,会依据所处的状态不同给executionContext
赋值不同的值,如赋值为BatchedContext
时阐明进入了更新合并阶段,而executionContext
默认状况下就是NoContext
。所以不在调度阶段时,React
就会进入无事可做的状态,就会将setState
同步执行。在React
处于本人的调度阶段,会执行诸如生命周期钩子函数,合成事件等,在这些状况下,会触发batchedUpdate
进行合并更新,所以此时将executionContext
赋值为BatchedContext
,那么天然就是异步的行为了。
在咱们的例子中,componentDidMount
属于React
调度流程的一部分,所以其中的setState
会被异步执行,componentDidMount
执行完后,React
就退出调度过程了,此时的executionContext
是 NoContext
了,然而咱们的代码还没有执行完,因为setTimeout
是一个异步办法,等它的回调被执行的时候,React
就会以同步的形式执行setState
了。
setState与useState
useState是React为函数组件赋予状态的一种做法,然而它和setState的调用栈有重合的中央,也就是都会进入scheduleUpdateOnFiber函数,所以在同步异步的行为上是没有区别的。