网站建设常州天堂在线资源种子
本文内容均针对于18.x以下版本
setState 到底是同步还是异步?很多人可能都有这种经历,面试的时候面试官给了你一段代码,让你说出输出的内容,比如这样:
constructor(props) {super(props);this.state = {val: '0'}}componentDidMount() {this.setState({data: '1'})console.log("val: ", this.state.val);// val: 0setTimeout(() => {this.setState({data: '2'})console.log("setTimeout ", this.state.val);//val:2})} 
 而这段代码的输出结果,第一个 console.log 会输出0 ,而第二个 console.log 会输出2 。也就是第一次 setState 的时候,它是异步的,第二次 setState 的时候,它又变成了同步的。
这是为什么呢?
只要你进入了
react的调度流程,那就是异步的。只要你没有进入react的调度流程,那就是同步的。什么东西不会进入react的调度流程?setTimeoutsetInterval,直接在DOM上绑定原生事件等。这些都不会走React的调度流程,你在这种情况下调用setState,那这次setState就是同步的。 否则就是异步的。而
setState同步执行的情况下,DOM也会被同步更新,也就意味着如果你多次setState,会导致多次更新,这是毫无意义并且浪费性能的。
 setState 被调用后最终会走到 scheduleUpdateOnFiber 这个函数里面来,下面是源码:
function scheduleUpdateOnFiber(fiber, expirationTime) {checkForNestedUpdates();warnAboutRenderPhaseUpdatesInDEV(fiber);var root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);if (root === null) {warnAboutUpdateOnUnmountedFiberInDEV(fiber);return;}checkForInterruption(fiber, expirationTime);recordScheduleUpdate(); // TODO: computeExpirationForFiber also reads the priority. Pass the// priority as an argument to that function and this one.var priorityLevel = getCurrentPriorityLevel();if (expirationTime === Sync) {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, expirationTime); // This is a legacy edge case. The initial mount of a ReactDOM.render-ed// root inside of batchedUpdates should be synchronous, but layout updates// should be deferred until the end of the batch.performSyncWorkOnRoot(root);} else {ensureRootIsScheduled(root);schedulePendingInteractions(root, expirationTime);// 重点!!!!!!if (executionContext === NoContext) {// Flush the synchronous work now, unless we're already working or inside// a batch. This is intentionally inside scheduleUpdateOnFiber instead of// scheduleCallbackForFiber to preserve the ability to schedule a callback// without immediately flushing it. We only do this for user-initiated// updates, to preserve historical behavior of legacy mode.flushSyncCallbackQueue();}}} else {ensureRootIsScheduled(root);schedulePendingInteractions(root, expirationTime);}if ((executionContext & DiscreteEventContext) !== NoContext && ( // Only updates at user-blocking priority or greater are considered// discrete, even inside a discrete event.priorityLevel === UserBlockingPriority$1 || priorityLevel === ImmediatePriority)) {// This is the result of a discrete event. Track the lowest priority// discrete update per root so we can flush them early, if needed.if (rootsWithPendingDiscreteUpdates === null) {rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);} else {var lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {rootsWithPendingDiscreteUpdates.set(root, expirationTime);}}}
}
 
我们着重看这段代码:
if (executionContext === NoContext) {// Flush the synchronous work now, unless we're already working or inside// a batch. This is intentionally inside scheduleUpdateOnFiber instead of// scheduleCallbackForFiber to preserve the ability to schedule a callback// without immediately flushing it. We only do this for user-initiated// updates, to preserve historical behavior of legacy mode.flushSyncCallbackQueue();
}
 
executionContext 代表了目前 react 所处的阶段,而 NoContext 你可以理解为是 react 已经没活干了的状态。而 flushSyncCallbackQueue 里面就会去同步调用我们的 this.setState ,也就是说会同步更新我们的 state 。所以,我们知道了,当 executionContext 为 NoContext 的时候,我们的 setState 就是同步的。那什么地方会改变 executionContext 的值呢?
我们随便找几个地方看看
function batchedEventUpdates$1(fn, a) {var prevExecutionContext = executionContext;executionContext |= EventContext;...省略
}function batchedUpdates$1(fn, a) {var prevExecutionContext = executionContext;executionContext |= BatchedContext;...省略
}
 
当 react 进入它自己的调度步骤时,会给这个 executionContext 赋予不同的值,表示不同的操作以及当前所处的状态,而 executionContext 的初始值就是 NoContext ,所以只要你不进入 react 的调度流程,这个值就是 NoContext ,那你的 setState 就是同步的。
