Commit 阶段概述
在 React 源码分析(二)—— Fiber 的 render 阶段 讲到, Fiber
在render
阶段完成后,对应 Fiber
的 DOM
结构和 EffectList
也被顺利的挂载在Fiber
上。当render
阶段完成后,便进入了 Fiber
的 commit
阶段。
function performConcurrentWorkOnRoot(root, didTimeout) {
// render 阶段完成, 准备进入 commit 阶段
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
finishConcurrentRender(root, exitStatus, lanes);
}
function finishConcurrentRender(root, exitStatus, lanes) {
switch (exitStatus) {
case RootCompleted: {
// The work completed. Ready to commit.
commitRoot(root);
break;
}
}
}
commitRoot
为 commit
阶段的起点,commit
阶段为同步逻辑,不会使用concurrent
模式的时间分片功能。commitRoot
的主要流程如下
commit 阶段详解
处理上次 render 产生的 Effect
function commitRootImpl(root, renderPriorityLevel) {
do {
flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);
}
commit
阶段的开始时间会去处理上次 render
阶段注册 & 还未被完成的 effect
异步处理函数。while
循环保证如果异步函数中执行了新的 setState
能在 commit
前置完成。
挂载 Effect 的异步回调函数
如果 Root Fiber
的 subtreeFlags
或 flags
不为 NoFlags
的话,说明当前Root Fiber
存在 Effect
需要执行。执行 flushPassiveEffects
注册异步回调函数。
subtreeFlags
的处理逻辑在render
阶段中的completeWork
阶段。
if (
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
(finishedWork.flags & PassiveMask) !== NoFlags
) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
pendingPassiveEffectsRemainingLanes = remainingLanes;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}
重点是 flushPassiveEffects
函数,在处理上次 render 产生的 Effect 时已经调用过。他的作用是处理useEffect
的 create/destory
逻辑。
export function flushPassiveEffects(): boolean {
return flushPassiveEffectsImpl();
}
function flushPassiveEffectsImpl() {
// 处理 hook 的 destory 函数
commitPassiveUnmountEffects(root.current);
// 处理 hook 的 create 函数
commitPassiveMountEffects(root, root.current);
}
commitPassiveUnmountEffects
和 commitPassiveMountEffect
的逻辑比较一致, 深度优先遍历,并在 begin
和 complete
执行不同的逻辑,这里以 commitPassiveUnmountEffects
为例子。
function commitPassiveUnmountEffects_begin() {
while (nextEffect !== null) {
const fiber = nextEffect;
const child = fiber.child;
if ((fiber.subtreeFlags & PassiveMask) !== NoFlags && child !== null) {
ensureCorrectReturnPointer(child, fiber);
nextEffect = child;
} else {
commitPassiveUnmountEffects_complete();
}
}
}
function commitPassiveUnmountEffects_complete() {
while (nextEffect !== null) {
const fiber = nextEffect;
if ((fiber.flags & Passive) !== NoFlags) {
commitPassiveUnmountOnFiber(fiber);
}
const sibling = fiber.sibling;
if (sibling !== null) {
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
// 执行 `EffectFlag` 为 HookPassive 的函数
commitHookEffectListUnmount(
HookPassive | HookHasEffect,
finishedWork,
finishedWork.return,
);
}
}
}
BeforeMutation 阶段的处理
BeforeMutation 是 commit 流程的第一个阶段,这个阶段执行于 浏览区渲染DOM(Mutation阶段)之前。在这个阶段中会进行
getSnapshotBeforeUpdate
生命周期的处理。
beforeMutation
的起点函数为commitBeforeMutatuonEffects
const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
root,
finishedWork,
);
commitBeforeMutatuonEffects
会从Root Fiber
用深度遍历交替执行 begin
和 complete
函数。
begin
阶段: 没有特殊的处理逻辑。
complete
阶段:去执行 commitBeforeMutationEffectsOnFiber
逻辑。这里的深度遍历逻辑与 render
阶段的 beginWork/completeWork
逻辑十分类似。
export function commitBeforeMutationEffects(
root: FiberRoot,
firstChild: Fiber,
) {
...
nextEffect = firstChild;
commitBeforeMutationEffects_begin();
...
}
function commitBeforeMutationEffects_begin() {
while (nextEffect !== null) {
const fiber = nextEffect;
const child = fiber.child;
if (
(fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
child !== null
) {
ensureCorrectReturnPointer(child, fiber);
nextEffect = child;
} else {
commitBeforeMutationEffects_complete();
}
}
}
function commitBeforeMutationEffects_complete() {
while (nextEffect !== null) {
const fiber = nextEffect;
commitBeforeMutationEffectsOnFiber(fiber);
const sibling = fiber.sibling;
if (sibling !== null) {
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
commitBeforeMutationEffectsOnFiber
的逻辑比较简单,判断当前 Fiber
是否为存在 Snapshot
这个 Flag
。如果有,则执行对应的getSnapshotBeforeUpdate
函数。
function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
if ((flags & Snapshot) !== NoFlags) {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
case HostComponent:
case HostText:
case HostPortal:
case IncompleteClassComponent:{
// Nothing to do for these component types
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.
const snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState,
);
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
break;
}
case HostRoot: {
if (supportsMutation) {
const root = finishedWork.stateNode;
clearContainer(root.containerInfo);
}
break;
}
default: {
throw new Error(
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
}
}
}
Mutation 阶段的处理
beforeMutation
处理完成后,便会执行Mutation
阶段的逻辑。在这个阶段React
会将WorkInProgress
上的Fiber
渲染在浏览器上。
与beforeMutation
类似, Mutatio
的起点函数为commitMutationEffects
const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
root,
finishedWork,
);
// The next phase is the mutation phase, where we mutate the host tree.
commitMutationEffects(root, finishedWork, lanes);
commitMutationEffects
会深度遍历完成 begin
和 complete
阶段。
export function commitMutationEffects(
root: FiberRoot,
firstChild: Fiber,
committedLanes: Lanes,
) {
inProgressLanes = committedLanes;
inProgressRoot = root;
nextEffect = firstChild;
commitMutationEffects_begin(root);
inProgressLanes = null;
inProgressRoot = null;
}
function commitMutationEffects_begin(root: FiberRoot) {
while (nextEffect !== null) {
const fiber = nextEffect;
// 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 childToDelete = deletions[i];
commitDeletion(root, childToDelete, fiber);
}
}
const child = fiber.child;
if ((fiber.subtreeFlags & MutationMask) !== NoFlags && child !== null) {
nextEffect = child;
} else {
commitMutationEffects_complete(root);
}
}
}
function commitMutationEffects_complete(root: FiberRoot) {
while (nextEffect !== null) {
const fiber = nextEffect;
commitMutationEffectsOnFiber(fiber, root);
const sibling = fiber.sibling;
if (sibling !== null) {
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
begin
阶段 : 在 render
阶段中,Fiber
的部分 Child_Fiber
被打上delete
的标签,对应的 DOM
节点在此刻被删除。如果当前Fiber
的Child_Fiber
节点不存在Placement
、Update
、ChildDeletion
、ContentReset
等flag
时,执行 complete
阶段。
complete
阶段: complete
阶段会执行commitMutationEffectsOnFiber
处理Fiber
。commitMutationEffectsOnFiber
阶段根据flag
进行不同的处理。如 Placement
会将Fiber
对应的DOM
渲染在浏览器上。Update
会更新对应的DOM
节点。
function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) {
const flags = finishedWork.flags;
const primaryFlags = flags & (Placement | Update | Hydrating);
outer: switch (primaryFlags) {
case Placement: {
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
break;
}
case PlacementAndUpdate: {
// Placement
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
// Update
const current = finishedWork.alternate;
commitWork(current, finishedWork);
break;
}
case Update: {
const current = finishedWork.alternate;
commitWork(current, finishedWork);
break;
}
}
}
上面的commitWork
逻辑执行了useLayoutEffect
中destory
函数(即 useLayoutEffect的返回值)。逻辑如下,执行commitHookEffectListUnmount
处理所有 EffectTag
为 HookLayout | HookHasEffect 的Fiber
, 执行他的 destory
函数。
这里出现了一个全新的HookInsertion|HookHasEffect
标识 ,根据源码猜测可能是一个新的hooks useInsertionEffect
function commitWork(current: Fiber | null, finishedWork: Fiber): void {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
commitHookEffectListUnmount(
HookInsertion | HookHasEffect,
finishedWork,
finishedWork.return,
);
commitHookEffectListMount(HookInsertion | HookHasEffect, finishedWork);
commitHookEffectListUnmount(
HookLayout | HookHasEffect,
finishedWork,
finishedWork.return,
);
return;
}
case ClassComponent: {
return;
}
case HostComponent: {
const instance: Instance = finishedWork.stateNode;
if (instance != null) {
// Commit the work prepared earlier.
const newProps = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
const oldProps = current !== null ? current.memoizedProps : newProps;
const type = finishedWork.type;
// TODO: Type the updateQueue to be specific to host components.
const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
finishedWork.updateQueue = null;
if (updatePayload !== null) {
commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
finishedWork,
);
}
}
return;
}
case HostText: {
const textInstance: TextInstance = finishedWork.stateNode;
const newText: string = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
const oldText: string =
current !== null ? current.memoizedProps : newText;
commitTextUpdate(textInstance, oldText, newText);
return;
}
}
}
function commitHookEffectListUnmount(
flags: HookFlags,
finishedWork: Fiber,
nearestMountedAncestor: Fiber | null,
) {
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
// Unmount
const destroy = effect.destroy;
effect.destroy = undefined;
if (destroy !== undefined) {
safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
Layout 阶段的处理
Layout 阶段发生于浏览器渲染完成之后,主要功能包括
componentDidMount
,useLayoutEffect
的create
函数的执行。还有useEffect
的create/destory
异步函数的挂载。
layout
阶段的逻辑开始于 commitLayoutEffects
, 在 处理 layout
阶段的逻辑之前,将 root 的 current
指向 finishedWork(即workInProgress)
。
// 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.
root.current = finishedWork;
commitLayoutEffects(finishedWork, root, lanes);
类似的, commitLayoutEffects
存在 begin
和 complete
两个阶段。
begin
阶段: 无特殊逻辑
complete
阶段: 执行 commitLayoutEffectOnFiber
逻辑
额外提一下 OffscreenComponent 这个
flag
,这会是一个新的API,将来会用于实现类似于 Vue 中keep-alive
功能。
export function commitLayoutEffects(
finishedWork: Fiber,
root: FiberRoot,
committedLanes: Lanes,
): void {
commitLayoutEffects_begin(finishedWork, root, committedLanes);
}
function commitLayoutEffects_begin(
subtreeRoot: Fiber,
root: FiberRoot,
committedLanes: Lanes,
) {
// Suspense layout effects semantics don't change for legacy roots.
const isModernRoot = (subtreeRoot.mode & ConcurrentMode) !== NoMode;
while (nextEffect !== null) {
const fiber = nextEffect;
const firstChild = fiber.child;
if (
enableSuspenseLayoutEffectSemantics &&
fiber.tag === OffscreenComponent &&
isModernRoot
) {
...
}
if ((fiber.subtreeFlags & LayoutMask) !== NoFlags && firstChild !== null) {
ensureCorrectReturnPointer(firstChild, fiber);
nextEffect = firstChild;
} else {
commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes);
}
}
}
function commitLayoutMountEffects_complete(
subtreeRoot: Fiber,
root: FiberRoot,
committedLanes: Lanes,
) {
while (nextEffect !== null) {
const fiber = nextEffect;
if ((fiber.flags & LayoutMask) !== NoFlags) {
const current = fiber.alternate;
commitLayoutEffectOnFiber(root, current, fiber, committedLanes);
if (fiber === subtreeRoot) {
nextEffect = null;
return;
}
const sibling = fiber.sibling;
if (sibling !== null) {
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
commitLayoutEffectOnFiber
中执行了 Class Component
中的 componentDidMount/componentDidUpdate
函数和 Function Component
中useLayoutEffect
的 create
函数。代码如下
function commitLayoutEffectOnFiber(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
committedLanes: Lanes,
): void {
if ((finishedWork.flags & LayoutMask) !== NoFlags) {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
// 执行 函数组件的 useLayoutEffect
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
break;
}
case ClassComponent:
const instance = finishedWork.stateNode;
if (finishedWork.flags & Update) {
if (!offscreenSubtreeWasHidden) {
if (current === null) {
// mount 阶段执行 componentDidMount
instance.componentDidMount();
} else {
const prevProps =
finishedWork.elementType === finishedWork.type
? current.memoizedProps
: resolveDefaultProps(
finishedWork.type,
current.memoizedProps,
);
const prevState = current.memoizedState;
// 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.
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
)
}
}
const updateQueue: UpdateQueue<
*,
> | null = (finishedWork.updateQueue: any);
// 执行 setState 的第二个参数
commitUpdateQueue(finishedWork, updateQueue, instance);
}
break;
}
case HostRoot: {
// 执行 render 的第二个参数
const updateQueue: UpdateQueue<
*,
> | null = (finishedWork.updateQueue: any);
if (updateQueue !== null) {
let instance = null;
if (finishedWork.child !== null) {
switch (finishedWork.child.tag) {
case HostComponent:
instance = getPublicInstance(finishedWork.child.stateNode);
break;
case ClassComponent:
instance = finishedWork.child.stateNode;
break;
}
}
commitUpdateQueue(finishedWork, updateQueue, instance);
}
break;
}
}