前言
在 React源码解析之renderRoot概览 中,renderRoot()
的最后一段的switch...case
即进入到了commit
阶段:
switch (workInProgressRootExitStatus) { //... case RootErrored: { //... // If we're already rendering synchronously, commit the root in its // errored state. //commit阶段 return commitRoot.bind(null, root); } case RootSuspended: { //... // The work expired. Commit immediately. //commit阶段 return commitRoot.bind(null, root); } case RootSuspendedWithDelay: { //... // The work expired. Commit immediately. //commit阶段 return commitRoot.bind(null, root); } case RootCompleted: { // The work completed. Ready to commit. //... //commit阶段 return commitRoot.bind(null, root); } default: { invariant(false, 'Unknown root exit status.'); } } 复制代码
那本篇文章就来整体看下commitRoot()
/commitRootImpl()
的整体流程,之后的文章再细讲内部各个function
的源码。
一、commitRoot()
作用:
① 以最高优先级去执行commitRootImpl()
② 如果有脏作用的话,用一个callback
回调函数去清除掉它们
源码:
function commitRoot(root) { //ImmediatePriority,优先级为 99,最高优先级,立即执行 //bind函数,请看:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind //获取调度优先级,并临时替换当前的优先级,去执行传进来的 callback runWithPriority(ImmediatePriority, commitRootImpl.bind(null, root)); // If there are passive effects, schedule a callback to flush them. This goes // outside commitRootImpl so that it inherits the priority of the render. //如果还有脏作用的话,用一个 callback 回调函数去清除掉它们 //因为是在commitRootImpl()外执行的,所以会继承 render 时的优先级 if (rootWithPendingPassiveEffects !== null) { //获取render 时的优先级 //请看:[React源码解析之scheduleWork(上)](https://juejin.im/post/5d7fa983f265da03cf7ac048)中的「五、getCurrentPriorityLevel()」 const priorityLevel = getCurrentPriorityLevel(); //对callback进行包装处理,并更新调度队列的状态 //请看[React源码解析之scheduleWork(下)](https://juejin.im/post/5d885b75f265da03e83baaa7)中的[十、scheduleSyncCallback()]的解析 scheduleCallback(priorityLevel, () => { //清除脏作用 flushPassiveEffects(); return null; }); } return null; } 复制代码
解析:
(1) 执行runWithPriority()
,获取调度优先级,并临时替换当前的优先级,去执行传进来的 callback
由于ImmediatePriority
是最高等级的优先级,所以会立即执行commitRootImpl()
方法
① runWithPriority()
的源码如下:
//获取调度优先级,并临时替换当前的优先级,去执行传进来的 callback export function runWithPriority<T>( reactPriorityLevel: ReactPriorityLevel, fn: () => T, ): T { //获取调度优先级 const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel); //临时替换当前的优先级,去执行传进来的 callback return Scheduler_runWithPriority(priorityLevel, fn); } 复制代码
② reactPriorityToSchedulerPriority()
的源码如下:
//获取调度优先级 function reactPriorityToSchedulerPriority(reactPriorityLevel) { switch (reactPriorityLevel) { case ImmediatePriority: return Scheduler_ImmediatePriority; case UserBlockingPriority: return Scheduler_UserBlockingPriority; case NormalPriority: return Scheduler_NormalPriority; case LowPriority: return Scheduler_LowPriority; case IdlePriority: return Scheduler_IdlePriority; default: invariant(false, 'Unknown priority level.'); } } 复制代码
③ Scheduler_runWithPriority()
即unstable_runWithPriority()
,源码如下:
//临时替换当前的优先级,去执行传进来的 callback function unstable_runWithPriority(priorityLevel, eventHandler) { //默认是 NormalPriority switch (priorityLevel) { case ImmediatePriority: case UserBlockingPriority: case NormalPriority: case LowPriority: case IdlePriority: break; default: priorityLevel = NormalPriority; } //缓存当前优先级 currentPriorityLevel var previousPriorityLevel = currentPriorityLevel; //临时替换优先级,去执行 eventHandler() currentPriorityLevel = priorityLevel; //try 里 return 了,还是会执行 finally 内的语句 try { return eventHandler(); } finally { //恢复当前优先级为之前的优先级 currentPriorityLevel = previousPriorityLevel; } } 复制代码
(2) 执行完runWithPriority()
后,如果还有脏作用的话,用一个callback
回调函数去清除掉它们
① getCurrentPriorityLevel()
的作用是:获取render
时的优先级,
具体请看:
React源码解析之scheduleWork(上)中的「五、getCurrentPriorityLevel()
」
② scheduleCallback()
的作用是:对callback
进行包装处理,并更新调度队列的状态
具体请看:
React源码解析之scheduleWork(下)中的「十、scheduleSyncCallback()
」
③ 这个callback
回调函数就是flushPassiveEffects()
,作用是:清除脏作用
具体的源码解析,我们在以后的文章里讲。
(3) commitRoot()
的核心函数是commitRootImpl()
,接下来就来看下它的整体结构及流程。
二、commitRootImpl()
作用:
(1) 根据effect
链判断是否进行commit
① 当执行commit
时,进行「before mutation
」、「mutation
」和「layout
」三个子阶段
② 否则快速过掉commit
阶段,走个 report 流程
(2) 判断本次commit
是否会产生新的更新,也就是脏作用,如果有脏作用则处理它
(3) 检查目标fiber
是否有剩余的work
要做
① 如果有剩余的work
的话,执行这些调度任务
② 没有的话,说明也没有报错,清除「错误边界」
(4) 刷新同步队列
源码:
function commitRootImpl(root) { //清除脏作用 flushPassiveEffects(); //dev 代码可不看 //flushRenderPhaseStrictModeWarningsInDEV(); //flushSuspensePriorityWarningInDEV(); //===context判断==== invariant( (executionContext & (RenderContext | CommitContext)) === NoContext, 'Should not already be working.', ); //调度完的任务 const finishedWork = root.finishedWork; //调度完的优先级 const expirationTime = root.finishedExpirationTime; //表示该节点没有要更新的任务,直接 return if (finishedWork === null) { return null; } //赋值给变量 finishedWork、expirationTime 后重置成初始值 //因为下面在对finishedWork、expirationTime 进行 commit后,任务就完成了 root.finishedWork = null; root.finishedExpirationTime = NoWork; //error 判断 invariant( finishedWork !== root.current, 'Cannot commit the same tree as before. This error is likely caused by ' + 'a bug in React. Please file an issue.', ); // commitRoot never returns a continuation; it always finishes synchronously. // So we can clear these now to allow a new callback to be scheduled. //commitRoot 是最后阶段,不会再被异步调用了,所以会清除 callback 相关的属性 root.callbackNode = null; root.callbackExpirationTime = NoWork; //计时器,可跳过 startCommitTimer(); // Update the first and last pending times on this root. The new first // pending time is whatever is left on the root fiber. //目标节点的更新优先级 const updateExpirationTimeBeforeCommit = finishedWork.expirationTime; //子节点的更新优先级,也就是所有子节点中优先级最高的任务 //关于 childExpirationTime,请看:https://juejin.im/post/5dcdfee86fb9a01ff600fe1d const childExpirationTimeBeforeCommit = finishedWork.childExpirationTime; //获取优先级最高的 expirationTime const firstPendingTimeBeforeCommit = childExpirationTimeBeforeCommit > updateExpirationTimeBeforeCommit ? childExpirationTimeBeforeCommit : updateExpirationTimeBeforeCommit; //firstPendingTime即优先级最高的任务的 expirationTime root.firstPendingTime = firstPendingTimeBeforeCommit; //如果firstPendingTime<lastPendingTime的话,一般意味着所有的更新任务都已经完成了,更新lastPendingTime if (firstPendingTimeBeforeCommit < root.lastPendingTime) { // This usually means we've finished all the work, but it can also happen // when something gets downprioritized during render, like a hidden tree. root.lastPendingTime = firstPendingTimeBeforeCommit; } //如果目标节点root就是正在更新的节点 workInProgressRoot 的话 //将相关值置为初始值,因为接下来会完成它的更新操作 if (root === workInProgressRoot) { // We can reset these now that they are finished. workInProgressRoot = null; workInProgress = null; renderExpirationTime = NoWork; } else { // This indicates that the last root we worked on is not the same one that // we're committing now. This most commonly happens when a suspended root // times out. } // Get the list of effects. //获取 effect 链 let firstEffect; //如果RootFiber 的 effectTag 有值的话,也就是说RootFiber也要commit的话 //将它的 finishedWork 也插入到 effect 链上,放到effect 链的最后 lastEffect.nextEffect 上 if (finishedWork.effectTag > PerformedWork) { // A fiber's effect list consists only of its children, not itself. So if // the root has an effect, we need to add it to the end of the list. The // resulting list is the set that would belong to the root's parent, if it // had one; that is, all the effects in the tree including the root. if (finishedWork.lastEffect !== null) { finishedWork.lastEffect.nextEffect = finishedWork; firstEffect = finishedWork.firstEffect; } else { firstEffect = finishedWork; } } else { // There is no effect on the root. firstEffect = finishedWork.firstEffect; } //effect 链上第一个需要更新的 fiber 对象 if (firstEffect !== null) { //=======context 相关,暂时跳过========= // const prevExecutionContext = executionContext; // executionContext |= CommitContext; // let prevInteractions: Set<Interaction> | null = null; // if (enableSchedulerTracing) { // prevInteractions = __interactionsRef.current; // __interactionsRef.current = root.memoizedInteractions; // } // Reset this to null before calling lifecycles ReactCurrentOwner.current = null; // The commit phase is broken into several sub-phases. We do a separate pass // of the effect list for each phase: all mutation effects come before all // layout effects, and so on. // 提交阶段分为几个子阶段。我们对每个阶段的效果列表进行单独的遍历:所有的mutation(突变)效果都在所有的layout效果之前 // The first phase a "before mutation" phase. We use this phase to read the // state of the host tree right before we mutate it. This is where // getSnapshotBeforeUpdate is called. //第一个子阶段是「在mutation突变之前」阶段,在这个阶段 React 会读取 fiber 树的 state 状态, //也是用 getSnapshotBeforeUpdate 命名的原因 //标记开始进行「before mutation」子阶段了 startCommitSnapshotEffectsTimer(); //更新当前选中的DOM节点,一般为 document.activeElement || document.body prepareForCommit(root.containerInfo); nextEffect = firstEffect; //===========第一个 while 循环============== do { if (__DEV__) { //删除了 dev 代码 } else { try { //调用 classComponent 上的生命周期方法 getSnapshotBeforeUpdate //关于getSnapshotBeforeUpdate,请看:https://zh-hans.reactjs.org/docs/react-component.html#getsnapshotbeforeupdate commitBeforeMutationEffects(); } catch (error) { invariant(nextEffect !== null, 'Should be working on an effect.'); captureCommitPhaseError(nextEffect, error); nextEffect = nextEffect.nextEffect; } } } while (nextEffect !== null); //标记「before mutation」子阶段已经结束 stopCommitSnapshotEffectsTimer(); //======profiler相关,暂时跳过====== if (enableProfilerTimer) { // Mark the current commit time to be shared by all Profilers in this // batch. This enables them to be grouped later. recordCommitTime(); } // The next phase is the mutation phase, where we mutate the host tree. //标记开始进行「mutation」子阶段了 startCommitHostEffectsTimer(); nextEffect = firstEffect; //=============第二个 while 循环================= do { if (__DEV__) { //删除了 dev 代码 } else { try { //提交HostComponent的 side effect,也就是 DOM 节点的操作(增删改) commitMutationEffects(); } catch (error) { invariant(nextEffect !== null, 'Should be working on an effect.'); captureCommitPhaseError(nextEffect, error); nextEffect = nextEffect.nextEffect; } } } while (nextEffect !== null); //标记「mutation」子阶段已经结束 stopCommitHostEffectsTimer(); //当进行 DOM 操作时,比如删除,可能会丢失选中 DOM 的焦点,此方法能保存丢失的值 resetAfterCommit(root.containerInfo); // The work-in-progress tree is now the current tree. This must come after // the mutation phase, so that the previous tree is still current during // componentWillUnmount, but before the layout phase, so that the finished // work is current during componentDidMount/Update. //在「mutation」子阶段后,正在进行的fiber树(work-in-progress tree)就成了 current tree //以便在 componentWillUnmount 期间,保证 先前的 fiber 树是 current tree //以便在「layout」子阶段之前,保证 work-in-progress 的 finishedWork 是 current //没看懂注释,大概意思应该是随着不同子阶段的进行,及时更新 root.current,也就是当前的 fiber 树更新成正在执行 commit 的 fiber 树 root.current = finishedWork; // The next phase is the layout phase, where we call effects that read // the host tree after it's been mutated. The idiomatic use case for this is // layout, but class component lifecycles also fire here for legacy reasons. //标记开始进行「layout」子阶段了 //这个阶段会触发所有组件的生命周期(lifecycles)的提交 startCommitLifeCyclesTimer(); nextEffect = firstEffect; //=============第三个 while 循环========================== do { if (__DEV__) { //删除了 dev 代码 } else { try { //commit lifecycles,也就是触发生命周期的 api commitLayoutEffects(root, expirationTime); } catch (error) { invariant(nextEffect !== null, 'Should be working on an effect.'); captureCommitPhaseError(nextEffect, error); nextEffect = nextEffect.nextEffect; } } } while (nextEffect !== null); //标记「layout」子阶段已经结束 stopCommitLifeCyclesTimer(); //正在 commit 的 effect 置为 null,表示 commit 结束 nextEffect = null; // Tell Scheduler to yield at the end of the frame, so the browser has an // opportunity to paint. //React 占用的资源已结束,告知浏览器可以去绘制 ui 了 requestPaint(); //=======暂时跳过============= if (enableSchedulerTracing) { __interactionsRef.current = ((prevInteractions: any): Set<Interaction>); } executionContext = prevExecutionContext; } //如果 effect 链没有需要更新的 fiber 对象 else { // No effects. root.current = finishedWork; // Measure these anyway so the flamegraph explicitly shows that there were // no effects. // TODO: Maybe there's a better way to report this. //快速过掉 commit 阶段,走个 report 流程 startCommitSnapshotEffectsTimer(); stopCommitSnapshotEffectsTimer(); if (enableProfilerTimer) { recordCommitTime(); } startCommitHostEffectsTimer(); stopCommitHostEffectsTimer(); startCommitLifeCyclesTimer(); stopCommitLifeCyclesTimer(); } //标记 commit 阶段结束 stopCommitTimer(); //判断本次 commit 是否会产生新的更新,也就是脏作用 const rootDidHavePassiveEffects = rootDoesHavePassiveEffects; //如果有脏作用的处理 if (rootDoesHavePassiveEffects) { // This commit has passive effects. Stash a reference to them. But don't // schedule a callback until after flushing layout work. rootDoesHavePassiveEffects = false; rootWithPendingPassiveEffects = root; pendingPassiveEffectsExpirationTime = expirationTime; } // Check if there's remaining work on this root //检查是否有剩余的 work const remainingExpirationTime = root.firstPendingTime; //如果有剩余的 work 的话 if (remainingExpirationTime !== NoWork) { //计算当前时间 const currentTime = requestCurrentTime(); //通过 expirationTime 推断优先级 const priorityLevel = inferPriorityFromExpirationTime( currentTime, remainingExpirationTime, ); if (enableSchedulerTracing) { //render 阶段衍生的 work,可能指新的 update 或者新的 error if (spawnedWorkDuringRender !== null) { const expirationTimes = spawnedWorkDuringRender; spawnedWorkDuringRender = null; //循环执行 scheduleInteractions for (let i = 0; i < expirationTimes.length; i++) { //与schedule的交互 //请看:[React源码解析之scheduleWork(上)](https://juejin.im/post/5d7fa983f265da03cf7ac048)中的「六、schedulePendingInteractions()」 scheduleInteractions( root, expirationTimes[i], root.memoizedInteractions, ); } } } // 同步调用callback // 流程是在root上存取callback和expirationTime, // 当新的callback调用时,比较更新expirationTime //请看:[React源码解析之scheduleWork(下)](https://juejin.im/post/5d885b75f265da03e83baaa7)中的「八、scheduleCallbackForRoot()」 scheduleCallbackForRoot(root, priorityLevel, remainingExpirationTime); } //如果没有剩余的 work 的话,说明 commit 成功,那么就清除「错误边界」的 list else { // If there's no remaining work, we can clear the set of already failed // error boundaries. legacyErrorBoundariesThatAlreadyFailed = null; } if (enableSchedulerTracing) { //当本次 commit 产生的脏作用被清除后,React就可以清除已经完成的交互 if (!rootDidHavePassiveEffects) { // If there are no passive effects, then we can complete the pending interactions. // Otherwise, we'll wait until after the passive effects are flushed. // Wait to do this until after remaining work has been scheduled, // so that we don't prematurely signal complete for interactions when there's e.g. hidden work. //清除已经完成的交互,如果被 suspended 挂起的话,把交互留到后续呈现 finishPendingInteractions(root, expirationTime); } } //devTools 相关的,可不看 onCommitRoot(finishedWork.stateNode, expirationTime); //剩余的 work 是同步任务的话 if (remainingExpirationTime === Sync) { // Count the number of times the root synchronously re-renders without // finishing. If there are too many, it indicates an infinite update loop. //计算同步 re-render 重新渲染的次数,判断是否是无限循环 if (root === rootWithNestedUpdates) { nestedUpdateCount++; } else { nestedUpdateCount = 0; rootWithNestedUpdates = root; } } else { nestedUpdateCount = 0; } //如果捕获到错误的话,就 throw error if (hasUncaughtError) { hasUncaughtError = false; const error = firstUncaughtError; firstUncaughtError = null; throw error; } //可不看 if ((executionContext & LegacyUnbatchedContext) !== NoContext) { // This is a legacy edge case. We just committed the initial mount of // a ReactDOM.render-ed root inside of batchedUpdates. The commit fired // synchronously, but layout updates should be deferred until the end // of the batch. return null; } // If layout work was scheduled, flush it now. //「layout」阶段的任务已经被调度的话,立即清除它 //刷新同步任务队列 //请看:[React源码解析之scheduleWork(下)](https://juejin.im/post/5d885b75f265da03e83baaa7)中的「十二、flushSyncCallbackQueue()」 flushSyncCallbackQueue(); return null; } 复制代码
解析:
虽然很长,但核心部分是那三个子阶段,也就是三个do...while
循环。
整体可以看成三个部分:
(1) 准备部分
(2) commit
部分(三个子阶段)
(3) 收尾部分
按源码顺序(有些我直接写进注释里了,就不讲了),看下commitRootImpl()
做了些什么:
(1) 执行flushPassiveEffects()
,清除脏作用
(2) 根据目标节点的更新优先级expirationTime
和子节点的更新优先级childExpirationTime
,来比较获取优先级最高的expirationTime
,借此来判断是否所有render
阶段的work
都已完成。
① 关于childExpirationTime
,请看:
React之childExpirationTime
(3) 判断目标 fiber自身是否也需要 commit,需要的话,则进行链表操作,把它的finishedWork
放effect
链的最后——lastEffect.nextEffect
上
(4) 如果firstEffect
不为 null 的话,说明有提交任务,则进行三个子阶段
① 第一个子阶段「before mutation
」
执行commitBeforeMutationEffects()
,本质是调用classComponent
上的生命周期方法——getSnapshotBeforeUpdate()
关于getSnapshotBeforeUpdate
的介绍及作用,请看:
zh-hans.reactjs.org/docs/react-…
② 第二个子阶段「mutation
」
执行commitMutationEffects()
,作用是:提交HostComponent
的side effect
,也就是DOM
节点的操作(增删改)
关于HostComponent
的相关知识,请看:
React源码解析之HostComponent的更新(上)
③ 第三个子阶段「layout
」
执行commitLayoutEffects()
,作用是:触发组件生命周期的api
(5) 如果firstEffect
为 null 的话,说明effect
链没有需要更新的fiber
对象,那么就快速过掉 commit
阶段,走个 report 流程
所以会看到三组startCommitXXXTimer()
、endCommitXXXTimer()
(6) 至此,commit 基本结束了,但是 commit 阶段可能也会产生新的 work,即remainingExpirationTime
当有剩余的 work 的话,循环它们,依次执行scheduleInteractions()
,排到调度任务中去,并通过scheduleCallbackForRoot()
去执行它们
① 关于scheduleInteractions()
,请看:
React源码解析之scheduleWork(上)中的「六、schedulePendingInteractions()
」
② 关于scheduleCallbackForRoot()
,请看:
React源码解析之scheduleWork(下)中的「八、scheduleCallbackForRoot()
」
(7) 最后,执行flushSyncCallbackQueue()
,刷新同步任务队列
① 关于flushSyncCallbackQueue()
,请看:
React源码解析之scheduleWork(下)中的「十二、flushSyncCallbackQueue()
」
(8) 最后,我在源码上的每行都写了注释,希望能帮你熟悉commitRoot
的整体流程。
GitHubcommitRoot()
/commitRootImpl()
:
github.com/AttackXiaoJ…