React Fiber 树构建与更新简析
前言
React Fiber 是 React 维护组件树的一整套机制,分为 render 阶段与 commit 阶段。
render 阶段是可中断的,借助了 Scheduler 包实现分时间片运行,会利用 current 树与 JSX 创建 workInProgress 树。
commit 阶段是同步的(不可中断),会将 workInProgress 树渲染到屏幕上(浏览器上是通过操作 DOM),并实现 current/workInProgress 树切换。
本文简要分析了 render 与 commit 阶段的 React 源码。

render 阶段
render 阶段主要调用 performSyncWorkOnRoot(同步更新) 与 performConcurrentWorkOnRoot(异步更新),他们分别会调用 workLoopSync 与 workLoopConcurrent。
1 2 3 4 5 6 7 8 9 10 11function workLoopSync() { while (workInProgress !== null) { performUnitOfWork(workInProgress); } } function workLoopConcurrent() { while (workInProgress !== null && !shouldYield()) { performUnitOfWork(workInProgress); } }
这两个方法都调用 performUnitOfWork,区别在于是否调用 shouldYield。shouldYield 来自于 React Scheduler 包,用于确定是否应当将控制权归还浏览器。以 workLoopSync 为例,其中主流程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36const SOME_CONDITION = true as any; type Fiber = { sibling: Fiber | null; return: Fiber | null; child: Fiber | null; alternate: Fiber | null; // current 与 workInProgress 互指 }; function beginWork(current: Fiber | null, workInProgress: Fiber) { // ... return SOME_CONDITION ? null : workInProgress.child; } function completeWork(current: Fiber | null, workInProgress: Fiber) { return null; } function workLoopSync(workInProgress) { function loop(workInProgress) { // performUnitOfWork: const current = workInProgress.alternate; let childFiber = beginWork(current, workInProgress); // 返回 null 或 workInProgress.child if (childFiber !== null) { loop(childFiber); } // completeUnitOfWork: const next = completeWork(current, workInProgress); if (next) { loop(next); } const siblingFiber = workInProgress.sibling; if (siblingFiber !== null) { loop(siblingFiber); } } loop(workInProgress); }
注意,上述代码为表述清楚使用了递归实现,但源代码是非递归的! 从中可知这一算法对 Fiber 树做了 后根遍历,每一个 fiber 先进行 beginWork 再进行 completeWork。
beginWork
beginWork 通过传入当前节点以生成子节点,其中涉及了复用或创建子节点的问题,进一步涉及 diff 算法。下面将 beginWork 分为两部分来看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35function beginWork(current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes): Fiber | null { const updateLanes = workInProgress.lanes; // ... if (current !== null) { const oldProps = current.memoizedProps; const newProps = workInProgress.pendingProps; if (oldProps !== newProps || hasLegacyContextChanged() || /* ... DEV 情况 */) { didReceiveUpdate = true; } else if (!includesSomeLane(renderLanes, updateLanes)) { didReceiveUpdate = false; switch (workInProgress.tag) { case HostRoot: // ... case HostComponent: // ... // ... 其他 case } // 复用 fiber return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); } else { if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) { // This is a special case that only exists for legacy mode. // See https://github.com/facebook/react/pull/19216. didReceiveUpdate = true; } else { didReceiveUpdate = false; } } } else { didReceiveUpdate = false; } // ... 后半部分 }
前半部分简单判断是否需要更新,同时尝试在特定条件下复用 fiber。
didReceiveUpdate 标记了是否需要更新 fiber,可见当满足下列条件之一时需要更新:
- 前后两次 props 不相同(浅比较)
- context 发生改变
当不需要更新且满足当前 fiber 优先级不够时,调用 bailoutOnAlreadyFinishedWork 复用 fiber,这一方法在该 fiber 及其子 fiber 没有 pending work 时返回 null,对应于 workLoopSync 可以不用继续向下遍历子树,做了 剪枝 操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45function beginWork( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes ): Fiber | null { // ... 前半部分 workInProgress.lanes = NoLanes; switch (workInProgress.tag) { case IndeterminateComponent: // ... case LazyComponent: // ... case FunctionComponent: { const Component = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; const resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); return updateFunctionComponent( current, workInProgress, Component, resolvedProps, renderLanes ); } case ClassComponent: { const Component = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; const resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); return updateClassComponent( current, workInProgress, Component, resolvedProps, renderLanes ); } // ... 其他 case } // ... }
后半部分根据 tag 生成子 fiber。这里以 FunctionComponent(函数组件)为例,其调用了 updateFunctionComponent。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33function updateFunctionComponent( current, workInProgress, Component, nextProps: any, renderLanes ) { // ... let context; // ... let nextChildren; prepareToReadContext(workInProgress, renderLanes); // ... nextChildren = renderWithHooks( current, workInProgress, Component, nextProps, context, renderLanes ); if (current !== null && !didReceiveUpdate) { bailoutHooks(current, workInProgress, renderLanes); return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); } // React DevTools reads this flag. workInProgress.flags |= PerformedWork; reconcileChildren(current, workInProgress, nextChildren, renderLanes); return workInProgress.child; }
当 current 存在(即此次更新为 update,非 mount),且 didReceiveUpdate 为 true 时,复用 fiber 与 hook。否则,为此 fiber 打上 PerformedWork flag,后调用 reconcileChildren 做 diff 算法并打上相关的 flag(此部分在 Virtual DOM 与 Diff 算法简析 中已阐述)。
beginWork 完成后,这一节点对应的 fiber 子树被生成,其中的节点可能是新创建的,也可能是复用的。同时,各 fiber 节点可能存在 flag 用于标记他们需要被移动、创建、删除等信息。
completeWork
completeWork 完成根据 fiber 创建 DOM 树或对 实际 DOM 进行预处理(实际上这个说法是不准确的,因为 react 不止是针对浏览器,只是考虑使用 react-dom 这一 render 的情况下,是对 DOM 进行修改)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71function completeWork( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes ): Fiber | null { const newProps = workInProgress.pendingProps; switch (workInProgress.tag) { // ... 其他 case case ContextConsumer: case MemoComponent: // ... return null; case ClassComponent: // ... case HostRoot: // ... case HostComponent: { popHostContext(workInProgress); const rootContainerInstance = getRootHostContainer(); const type = workInProgress.type; if (current !== null && workInProgress.stateNode != null) { /* Update 逻辑 */ updateHostComponent( current, workInProgress, type, newProps, rootContainerInstance ); // ... ref 相关 } else { /* Mount 逻辑 */ // ... const currentHostContext = getHostContext(); const wasHydrated = popHydrationState(workInProgress); if (wasHydrated) { // ... hydrated 时的情况 } else { const instance = createInstance( type, newProps, rootContainerInstance, currentHostContext, workInProgress ); appendAllChildren(instance, workInProgress, false, false); workInProgress.stateNode = instance; if ( finalizeInitialChildren( instance, type, newProps, rootContainerInstance, currentHostContext ) ) { markUpdate(workInProgress); } } // ... ref 相关 } // ... return null; } // ... 其他 case } // ... }
以 HostComponent 为例,其以 current !== null && workInProgress.stateNode != null 为区分执行逻辑,实际上该条件满足说明 DOM 节点已存在,执行 update 逻辑,否则执行 mount 逻辑。
Mount 逻辑
在 mount 的情况下,
createInstance创建 DOM 节点appendAllChildren将所有子节点对应的 DOM 节点插入到自己中(因为completeWork在回溯阶段,因此孩子节点的逻辑已经执行完成了,DOM 节点必然是存在的)finalizeInitialChildren设置 DOM 上的一些属性,此时会将 React 事件机制 中提到的 listenerWrapper 绑定到 rootContainer 上markUpdate会在 fiber 节点 autoFocus 时打上Updateflag
经过上述步骤后,生成了一颗离屏(offscreen)DOM 树。
Update 逻辑
在 update 的情况下,主要调用了 updateHostComponent。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27updateHostComponent = function ( current: Fiber, workInProgress: Fiber, type: Type, newProps: Props, rootContainerInstance: Container ) { const oldProps = current.memoizedProps; if (oldProps === newProps) { return; } const instance: Instance = workInProgress.stateNode; const currentHostContext = getHostContext(); const updatePayload = prepareUpdate( instance, type, oldProps, newProps, rootContainerInstance, currentHostContext ); workInProgress.updateQueue = updatePayload; if (updatePayload) { markUpdate(workInProgress); } };
其中主要将 updatePayload 挂载到了 workInProgress.updateQueue 上,待 commit 阶段处理。
updatePayload 是一个形如 [key1, value1, key2, value2, ...] 的数组,包含了 props 的更新,包括:
styleDANGEROUSLY_SET_INNER_HTML- 事件
- 为 string 或 number 的 children
- 其他普通的 prop
收集 effectList
completeWork 由 completeUnitOfWork 包裹,在 completeWork 结束后会为 returnFiber 收集 effectList。effectList 是单链表,其上挂载了自己子树(不包含自己)中需要在 commit 阶段处理 effect (即 fiber.flags)的 fiber。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30function completeUnitOfWork(unitOfWork: Fiber): void { // ... if ( returnFiber !== null && // Do not append effects to parents if a sibling failed to complete (returnFiber.flags & Incomplete) === NoFlags ) { if (returnFiber.firstEffect === null) { returnFiber.firstEffect = completedWork.firstEffect; } if (completedWork.lastEffect !== null) { if (returnFiber.lastEffect !== null) { returnFiber.lastEffect.nextEffect = completedWork.firstEffect; } returnFiber.lastEffect = completedWork.lastEffect; } const flags = completedWork.flags; // Skip both NoWork and PerformedWork tags when creating the effect if (flags > PerformedWork) { if (returnFiber.lastEffect !== null) { returnFiber.lastEffect.nextEffect = completedWork; } else { returnFiber.firstEffect = completedWork; } returnFiber.lastEffect = completedWork; } } }
错误处理
当 fiber 处理时发生错误抛出异常时,会被捕获。
1 2 3 4 5 6 7 8do { try { workLoopSync(); // workLoopConcurrent() 也是如此 break; } catch (thrownValue) { handleError(root, thrownValue); } } while (true);
handleError 中会调用 throwException,后再次对 workInProgress 调用 completeUnitOfWork。
1 2 3 4 5 6function handleError(root, thrownValue): void { let erroredWork = workInProgress; // ... throwException(/* ... */); completeUnitOfWork(erroredWork); }
在 throwException 中:
- 为出错的
fiber打上Incompleteflag; - 沿根查找第一个定义了
getDerivedStateFromError或componentDidCatch方法的 fiber(即 ErrorBoundary),直到HostRoot(未捕捉错误则向上抛),将这两个方法作为 update 推入 updateQueue;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72function createClassErrorUpdate( fiber: Fiber, errorInfo: CapturedValue<mixed>, lane: Lane ): Update<mixed> { const update = createUpdate(NoTimestamp, lane); update.tag = CaptureUpdate; // ... update.payload = () => { logCapturedError(fiber, errorInfo); return getDerivedStateFromError(error); }; // ... update.callback = function callback() { // ... this.componentDidCatch(error, { componentStack: stack !== null ? stack : "", }); // ... }; // ... return update; } function throwException( root: FiberRoot, returnFiber: Fiber, sourceFiber: Fiber, value: mixed, rootRenderLanes: Lanes ) { // The source fiber did not complete. sourceFiber.flags |= Incomplete; // Its effect list is no longer valid. sourceFiber.firstEffect = sourceFiber.lastEffect = null; // ... do { switch (workInProgress.tag) { case HostRoot: { // ... const update = createRootErrorUpdate(workInProgress, errorInfo, lane); enqueueCapturedUpdate(workInProgress, update); return; } case ClassComponent: // ... if ( (workInProgress.flags & DidCapture) === NoFlags && (typeof ctor.getDerivedStateFromError === "function" || (instance !== null && typeof instance.componentDidCatch === "function" && !isAlreadyFailedLegacyErrorBoundary(instance))) ) { workInProgress.flags |= ShouldCapture; // ... // Schedule the error boundary to re-render using updated state const update = createClassErrorUpdate( workInProgress, errorInfo, lane ); enqueueCapturedUpdate(workInProgress, update); return; } break; default: break; } workInProgress = workInProgress.return; } while (workInProgress !== null); // ... }
throwException 后走 completeUnitOfWork 逻辑,注意到此时 fiber 已经被打上了 Incomplete flag,此时会进入 completeUnitOfWork 的错误处理逻辑。其中:
- 调用
unwindWork处理错误,其当当前fiber有ShouldCapture时(即 ErrorBoundary),将 flag 转为DidCapture,后返回这一fiber作为下一个performUnitOfWork对象; - 如果没有返回新的
fiber,那么为returnFiber打上InComplete(未捕捉错误则向上抛);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47function completeUnitOfWork(unitOfWork: Fiber): void { // ... if ((completedWork.flags & Incomplete) === NoFlags) { // ... } else { const next = unwindWork(completedWork, subtreeRenderLanes); // ... if (next !== null) { // If completing this work spawned new work, do that next. We'll come back here again. // Since we're restarting, remove anything that is not a host effect from the effect tag. next.flags &= HostEffectMask; workInProgress = next; return; } // ... if (returnFiber !== null) { // Mark the parent fiber as incomplete and clear its effect list. returnFiber.firstEffect = returnFiber.lastEffect = null; returnFiber.flags |= Incomplete; } } // ... } function unwindWork(workInProgress: Fiber, renderLanes: Lanes) { switch (workInProgress.tag) { case ClassComponent: { const Component = workInProgress.type; // ... const flags = workInProgress.flags; if (flags & ShouldCapture) { workInProgress.flags = (flags & ~ShouldCapture) | DidCapture; // ... return workInProgress; } return null; } case HostRoot: // ... case HostComponent: // ... case SuspenseComponent: // ... // ... 其他 case } }
在 beginWork 处理 class 组件时:
- 通过处理
updateQueue以处理getDerivedStateFromError,componentDidCatch; - 存在
DidCaptureflag 且getDerivedStateFromError未定义时,令nextChildren为 null 以卸载组件树;
commit 阶段
commit 阶段从 commitRoot 开始。commitRoot 通过 Scheduler 包的中的 runWithPriority 执行 commitRootImpl。其中可按顺序划分为:
- before mutation 阶段:
commitBeforeMutationEffects实现; - mutation 阶段:
commitMutationEffects实现; - workInProgress/current 树切换:
root.current = finishedWork实现 workInProgress 树转 current 树; - layout 阶段:
recursivelyCommitLayoutEffects实现;
before mutation 阶段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29function commitBeforeMutationEffects() { while (nextEffect !== null) { const current = nextEffect.alternate; if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) { // 处理 focus, blur 逻辑 } const flags = nextEffect.flags; if ((flags & Snapshot) !== NoFlags) { // ... // 调用 `getSnapshotBeforeUpdate` commitBeforeMutationEffectOnFiber(current, nextEffect); // ... } if ((flags & Passive) !== NoFlags) { // If there are passive effects, schedule a callback to flush at // the earliest opportunity. if (!rootDoesHavePassiveEffects) { rootDoesHavePassiveEffects = true; scheduleCallback(NormalSchedulerPriority, () => { flushPassiveEffects(); return null; }); } } nextEffect = nextEffect.nextEffect; } }
commitBeforeMutationEffects 方法中主要做了三件事情:
- 处理
focus,blur相关逻辑 - 调用生命周期函数
getSnapshotBeforeUpdate - 异步调度
useEffect
调用 getSnapshotBeforeUpdate
在 commitBeforeMutationLifeCycles(commitBeforeMutationEffectOnFiber 的别名)中,当 fiber 为 ClassComponent 且有 Snapshot flag 时,会调用 getSnapshotBeforeUpdate(...) 生命周期函数。
处理 useEffect
flushPassiveEffects 负责处理 useEffect,其在 before mutation 中通过 scheduleCallback 异步调度。
flushPassiveEffects 会间接按顺序调用下列方法:
flushPassiveUnmountEffects:对 fiber, fiber.deletions 以后根遍历顺序处理 effect list 中有HookPassive,HookHasEffect的 effect 的destroy方法;flushPassiveMountEffects:对 fiber 以后根遍历顺序处理 effect list 中有HookPassive,HookHasEffect的 effect,调用create方法,并将返回值挂载在destroy上(这与useEffectAPI 用法一致);flushSyncCallbackQueue:由runWithPriority方法包裹,同步清除 syncQueue 中的 callback;
mutation 阶段
mutation 阶段由 commitMutationEffects 实现,其处理 fiber.deletions 后以后根遍历顺序调用 commitMutationEffectsImpl 处理 fiber。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55function commitMutationEffects( root: FiberRoot, renderPriorityLevel: ReactPriorityLevel ) { // TODO: Should probably move the bulk of this function to commitWork. while (nextEffect !== null) { // ... const flags = nextEffect.flags; if (flags & ContentReset) { commitResetTextContent(nextEffect); } if (flags & Ref) { const current = nextEffect.alternate; if (current !== null) { commitDetachRef(current); } // ... } const primaryFlags = flags & (Placement | Update | Deletion | Hydrating); switch (primaryFlags) { case Placement: { commitPlacement(nextEffect); nextEffect.flags &= ~Placement; break; } case PlacementAndUpdate: { // Placement commitPlacement(nextEffect); nextEffect.flags &= ~Placement; // Update const current = nextEffect.alternate; commitWork(current, nextEffect); break; } // hydrate 相关 case Update: { const current = nextEffect.alternate; commitWork(current, nextEffect); break; } case Deletion: { commitDeletion(root, nextEffect, renderPriorityLevel); break; } } // ... nextEffect = nextEffect.nextEffect; } }
commitMutationEffectsImpl 处理内容包括:
- 文本内容更新:
commitResetTextContent; - ref 解除:
commitDetachRef将current.ref设为 null; - DOM 节点插入或移动:
commitPlacement; - DOM 节点更新:
commitWork; - DOM 节点删除:
commitDeletion将自己从父元素中删除; - hydrate 相关;
commitPlacement
commitPlacement 负责 Fiber 节点对应的 DOM 节点的插入或移动。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52function commitPlacement(finishedWork: Fiber): void { // ... const parentFiber = getHostParentFiber(finishedWork); let parent; let isContainer; // ... if (parentFiber.flags & ContentReset) { resetTextContent(parent); parentFiber.flags &= ~ContentReset; } const before = getHostSibling(finishedWork); // ... insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent); // ... } function getHostSibling(fiber: Fiber): ?Instance { // We're going to search forward into the tree until we find a sibling host // node. Unfortunately, if multiple insertions are done in a row we have to // search past them. This leads to exponential search for the next sibling. // TODO: Find a more efficient way to do this. let node: Fiber = fiber; siblings: while (true) { while (node.sibling === null) { if (node.return === null || isHostParent(node.return)) { return null; } node = node.return; } node.sibling.return = node.return; node = node.sibling; while ( node.tag !== HostComponent && node.tag !== HostText && node.tag !== DehydratedFragment ) { if (node.flags & Placement) { continue siblings; } if (node.child === null || node.tag === HostPortal) { continue siblings; } else { node.child.return = node; node = node.child; } } if (!(node.flags & Placement)) { return node.stateNode; } } }
其中,
getHostParentFiber:找到 fiber 对应的可以作为 DOM 节点对应的 fiber,后将对应的 container 或 DOM 节点赋值给parent;getHostSibling:找到 fiber 的兄弟节点对应的 DOM 节点。由于 fiber 树与 DOM 树不对应(fiber 树中存在非HostComponent,HostText等节点),本算法每次执行的复杂度都为 $O(n)$,并且很可能跨层查找,效率不高。在本算法中,需要在不断的向上(沿根)、向右(沿兄弟)、向下(沿孩子)的步骤中找到固定的(没有Placementflag)符合条件的fiber,如果找不到则返回null;insertOrAppendPlacementNodeIntoContainer/insertOrAppendPlacementNode:根绝before是否 null 决定使用insertBefore或appendChildAPI;
commitWork
commitWork 负责 fiber 节点与实际 DOM 节点的更新。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55function commitWork(current: Fiber | null, finishedWork: Fiber): void { // ... switch (finishedWork.tag) { case FunctionComponent: case ForwardRef: case MemoComponent: case SimpleMemoComponent: case Block: { // ... commitHookEffectListUnmount( HookLayout | HookHasEffect, finishedWork, finishedWork.return ); return; } case ClassComponent: // ... case HostComponent: { const instance: Instance = finishedWork.stateNode; if (instance != null) { const newProps = finishedWork.memoizedProps; const oldProps = current !== null ? current.memoizedProps : newProps; const type = finishedWork.type; const updatePayload: null | UpdatePayload = finishedWork.updateQueue; 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; const oldText: string = current !== null ? current.memoizedProps : newText; commitTextUpdate(textInstance, oldText, newText); return; } case HostRoot: { // ... hydrate 相关 return; } // ... 其他 case } // ... }
其中,
- 对函数组件等:通过
commitHookEffectListUnmount(HookLayout | HookHasEffect, ...)处理useLayoutEffecteffect 的 destroy - 对 HostComponent:通过
commitUpdate(...)更新 DOM 节点上的 fiber props 信息,并根据 render 阶段的 completeWork 中在 update 时创建的 updatePayload 更新 DOM 节点上的相关信息,如style,innerHTML等 - ...
layout 阶段
layout 阶段由 recursivelyCommitLayoutEffects 实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) { // ... while (nextEffect !== null) { // ... const flags = nextEffect.flags; if (flags & (Update | Callback)) { const current = nextEffect.alternate; commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes); } // ... if (flags & Ref) { commitAttachRef(nextEffect); } // ... nextEffect = nextEffect.nextEffect; } // ... } function commitLifeCycles( finishedRoot: FiberRoot, current: Fiber | null, finishedWork: Fiber, committedLanes: Lanes ): void { switch (finishedWork.tag) { case FunctionComponent: case ForwardRef: case SimpleMemoComponent: case Block: { // ... commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork); // ... schedulePassiveEffects(finishedWork); return; } case ClassComponent: { const instance = finishedWork.stateNode; if (finishedWork.flags & Update) { if (current === null) { // ... instance.componentDidMount(); } else { const prevProps = finishedWork.elementType === finishedWork.type ? current.memoizedProps : resolveDefaultProps(finishedWork.type, current.memoizedProps); const prevState = current.memoizedState; // ... instance.componentDidUpdate( prevProps, prevState, instance.__reactInternalSnapshotBeforeUpdate ); } } // ... const updateQueue = finishedWork.updateQueue; if (updateQueue !== null) { // ... commitUpdateQueue(finishedWork, updateQueue, instance); } return; } // ... 其他 case } // ... }
在 commitLayoutEffectOnFiber(commitLifeCycles) 中,区分组件类型执行操作:
- 函数组件等:
- 调用
useLayoutEffect,通过commitHookEffectListMount(HookLayout | HookHasEffect, ...); - 异步调度还未调度的
useEffect;
- 调用
- class 组件:
- 当有
Updateflag 时,调用componentDidMount/componentDidUpdate; - 处理 updateQueue 中的 effect;
- 当有
HostRoot:- 处理 updateQueue 中的 effect;
HostComponent:- 当处于 mount 逻辑且存在
Updateflag 时,处理 autoFocus 逻辑;
- 当处于 mount 逻辑且存在
另外,当存在 Ref flag 时会将 ref.current 设置为这一 DOM 节点(通过 commitAttachRef)。