赞
踩
2021SC@SDUSC
前几篇我们介绍了render阶段,现在我们来看commit阶段,commitRoot方法是commit阶段工作的起点。fiberRootNode会作为传参。
同时在rootFiber.firstEffect上保存了一条需要执行副作用的Fiber节点的单向链表effectList,这些Fiber节点的updateQueue中保存了变化的props。
这些副作用对应的DOM操作在commit阶段执行。
除此之外,一些生命周期钩子(比如componentDidXXX)、hook(比如useEffect)也需要在commit阶段执行。
commit阶段的主要工作(即Renderer的工作流程)可以分为三部分:
1.before mutation阶段(执行DOM操作前)
2.mutation阶段(执行DOM操作)
3.layout阶段(执行DOM操作后)
commit阶段的入口是commitRoot函数,它会告知scheduler以立即执行的优先级去调度commit阶段的工作。
function commitRoot(root) { // TODO: This no longer makes any sense. We already wrap the mutation and // layout phases. Should be able to remove. const previousUpdateLanePriority = getCurrentUpdatePriority(); const prevTransition = ReactCurrentBatchConfig.transition; try { ReactCurrentBatchConfig.transition = 0; setCurrentUpdatePriority(DiscreteEventPriority); commitRootImpl(root, previousUpdateLanePriority); } finally { ReactCurrentBatchConfig.transition = prevTransition; setCurrentUpdatePriority(previousUpdateLanePriority); } return null; }
可以看到我们做的大部分工作其实都是在commitRootImpl中完成的。
注:其中代码中已经删去部分注释和内容,也加上了自己的理解,如果想查看完整版请自行去GitHub上下载react-main,谢谢
function commitRootImpl(root, renderPriorityLevel) { // 进入commit阶段,先执行一次之前未执行的useEffect do { flushPassiveEffects(); } while (rootWithPendingPassiveEffects !== null); // 准备阶段----------------------------------------------- const finishedWork = root.finishedWork; const lanes = root.finishedLanes; if (finishedWork === null) { return null; } root.finishedWork = null; root.finishedLanes = NoLanes; root.callbackNode = null; root.callbackId = NoLanes; // effectList的整理,将root上的effect连到effectList的末尾 let firstEffect; if (finishedWork.effectTag > PerformedWork) { 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; } // 准备阶段结束,开始处理effectList if (firstEffect !== null) { ... // before mutation阶段-------------------------------- nextEffect = firstEffect; do {...} while (nextEffect !== null); ... // mutation阶段--------------------------------------- nextEffect = firstEffect; do {...} while (nextEffect !== null); // 将wprkInProgress树切换为current树 root.current = finishedWork; // layout阶段----------------------------------------- nextEffect = firstEffect; do {...} while (nextEffect !== null); nextEffect = null; // 通知浏览器去绘制 requestPaint(); } else { // 没有effectList,直接将wprkInProgress树切换为current树 root.current = finishedWork; } const rootDidHavePassiveEffects = rootDoesHavePassiveEffects; // 获取尚未处理的优先级,比如之前被跳过的任务的优先级 remainingLanes = root.pendingLanes; // 将被跳过的优先级放到root上的pendingLanes(待处理的优先级)上 markRootFinished(root, remainingLanes); /* * 每次commit阶段完成后,再执行一遍ensureRootIsScheduled,确保是否还有任务需要被调度。 * 例如,高优先级插队的更新完成后,commit完成后,还会再执行一遍,保证之前跳过的低优先级任务 * 重新调度 * * */ ensureRootIsScheduled(root, now()); ... return null; }
下面我们对这上述三个阶段分别进行的详细讲解。
before mutation阶段的代码在commitRootlmpl中其实很短,整个过程就是遍历effectList并调用commitBeforeMutationEffects函数处理。
const prevTransition = ReactCurrentBatchConfig.transition; ReactCurrentBatchConfig.transition = 0; const previousPriority = getCurrentUpdatePriority(); setCurrentUpdatePriority(DiscreteEventPriority); const prevExecutionContext = executionContext; executionContext |= CommitContext; // 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. // 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. const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects( root, finishedWork, );
先保存之前的优先级,以同步优先级执行,执行完毕后恢复之前优先级
再将当前上下文标记为CommitContext,作为commit阶段的标志
我们重点关注beforeMutation阶段的主函数commitBeforeMutationEffects做了什么。
export function commitBeforeMutationEffects( root: FiberRoot, firstChild: Fiber, ) { focusedInstanceHandle = prepareForCommit(root.containerInfo); nextEffect = firstChild; commitBeforeMutationEffects_begin(); // We no longer need to track the active instance fiber const shouldFire = shouldFireAfterActiveInstanceBlur; shouldFireAfterActiveInstanceBlur = false; focusedInstanceHandle = null; return shouldFire; }
我们可以看到代码主要是调用了commitBeforeMutationEffects_begin函数
代码如下:
function commitBeforeMutationEffects_begin() { while (nextEffect !== null) { const fiber = nextEffect; // This phase is only used for beforeActiveInstanceBlur. // Let's skip the whole loop if it's off. if (enableCreateEventHandleAPI) { // TODO: Should wrap this in flags check, too, as optimization const deletions = fiber.deletions; if (deletions !== null) { for (let i = 0; i < deletions.length; i++) { const deletion = deletions[i]; commitBeforeMutationEffectsDeletion(deletion); } } } const child = fiber.child; if ( (fiber.subtreeFlags & BeforeMutationMask) !== NoFlags && child !== null ) { ensureCorrectReturnPointer(child, fiber); nextEffect = child; } else { commitBeforeMutationEffects_complete(); } } }
它的作用主要是调用commitBeforeMutationEffects_complete函数,然后在commitBeforeMutationEffects_complete中尝试调用commitBeforeMutationEffectsOnFiber(fiber);
function commitBeforeMutationEffects_complete() { while (nextEffect !== null) { const fiber = nextEffect; setCurrentDebugFiberInDEV(fiber); try { commitBeforeMutationEffectsOnFiber(fiber); } catch (error) { reportUncaughtErrorInDEV(error); captureCommitPhaseError(fiber, fiber.return, error); } resetCurrentDebugFiberInDEV(); const sibling = fiber.sibling; if (sibling !== null) { ensureCorrectReturnPointer(sibling, fiber.return); nextEffect = sibling; return; } nextEffect = fiber.return; } }
commitBeforeMutationEffectOnFiber是commitBeforeMutationLifeCycles的别名。在该方法内会调用类组件的getSnapshotBeforeUpdate,针对函数组件,异步调度useEffect。
注:代码已删去部分,只保留了switch部分。
function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) { switch (finishedWork.tag) { case FunctionComponent: case ForwardRef: case SimpleMemoComponent: { break; } case ClassComponent: { if (current !== null) { const prevProps = current.memoizedProps; const prevState = current.memoizedState; const instance = finishedWork.stateNode; // We could update instance props and state here, // but instead we rely on them being set during last render. // TODO: revisit this when we implement resuming. if (__DEV__) { if ( finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps ) { if (instance.props !== finishedWork.memoizedProps) { console.error( 'Expected %s props to match memoized props before ' + 'getSnapshotBeforeUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance', ); } if (instance.state !== finishedWork.memoizedState) { console.error( 'Expected %s state to match memoized state before ' + 'getSnapshotBeforeUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.state`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance', ); } } } const snapshot = instance.getSnapshotBeforeUpdate( finishedWork.elementType === finishedWork.type ? prevProps : resolveDefaultProps(finishedWork.type, prevProps), prevState, ); if (__DEV__) { const didWarnSet = ((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set<mixed>); if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) { didWarnSet.add(finishedWork.type); console.error( '%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' + 'must be returned. You have returned undefined.', getComponentNameFromFiber(finishedWork), ); } } instance.__reactInternalSnapshotBeforeUpdate = snapshot; } break; } case HostRoot: { if (supportsMutation) { const root = finishedWork.stateNode; clearContainer(root.containerInfo); } break; } case HostComponent: case HostText: case HostPortal: case IncompleteClassComponent: // Nothing to do for these component types break; default: { invariant( false, 'This unit of work tag should not have side-effects. This error is ' + 'likely caused by a bug in React. Please file an issue.', ); } } resetCurrentDebugFiberInDEV(); } }
我们可以看见,getSnapshotBeforeUpdate是在commit阶段内的before mutation阶段调用的,由于commit阶段是同步的,所以不会遇到多次调用的问题。
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
在这几行代码内,scheduleCallback方法由Scheduler模块提供,用于以某个优先级异步调度一个回调函数。
在此处,被异步调度的回调函数就是触发useEffect的方法flushPassiveEffects。
我们接下来讨论useEffect如何被异步调度:
export function flushPassiveEffects(): boolean { // Returns whether passive effects were flushed. // TODO: Combine this check with the one in flushPassiveEFfectsImpl. We should // probably just combine the two functions. I believe they were only separate // in the first place because we used to wrap it with // `Scheduler.runWithPriority`, which accepts a function. But now we track the // priority within React itself, so we can mutate the variable directly. if (rootWithPendingPassiveEffects !== null) { const renderPriority = lanesToEventPriority(pendingPassiveEffectsLanes); const priority = lowerEventPriority(DefaultEventPriority, renderPriority); const prevTransition = ReactCurrentBatchConfig.transition; const previousPriority = getCurrentUpdatePriority(); try { ReactCurrentBatchConfig.transition = 0; setCurrentUpdatePriority(priority); return flushPassiveEffectsImpl(); } finally { setCurrentUpdatePriority(previousPriority); ReactCurrentBatchConfig.transition = prevTransition; } } return false; }
在flushPassiveEffects方法内部会从全局变量rootWithPendingPassiveEffects获取effectList。
我们知道,effectList中保存了需要执行副作用的Fiber节点。其中副作用包括
1.插入DOM节点(Placement)
2.更新DOM节点(Update)
3.删除DOM节点(Deletion)
除此外,当一个FunctionComponent含有useEffect或useLayoutEffect,他对应的Fiber节点也会被赋值effectTag。
在flushPassiveEffects方法内部会遍历rootWithPendingPassiveEffects(即effectList)执行effect回调函数。
如果在此时直接执行,rootWithPendingPassiveEffects === null。
那么rootWithPendingPassiveEffects会在何时赋值呢?
这里我们会根据rootDoesHavePassiveEffects === true?决定是否赋值rootWithPendingPassiveEffects。
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;
pendingPassiveEffectsLanes = lanes;
}
所以整个useEffect异步调用分为三步:
1.before mutation阶段在scheduleCallback中调度flushPassiveEffects
2.layout阶段之后将effectList赋值给rootWithPendingPassiveEffects
3.scheduleCallback触发flushPassiveEffects,flushPassiveEffects内部遍历rootWithPendingPassiveEffects。
下一篇我们会继续讲解mutation阶段和layout阶段。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。