共计 2805 个字符,预计需要花费 8 分钟才能阅读完成。
本文基于 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 函数,所以在同步异步的行为上是没有区别的。