React Reconciler Commit阶段

Tags
Published
Author
commitWork前,会将在workloopSync中生成的workInProgressfiber树赋值给fiberRootfinishedWork属性。
const finishedWork: Fiber = (root.current.alternate: any); root.finishedWork = finishedWork; root.finishedLanes = lanes;

commitRootImpl(root, renderPriorityLevel)的源码

在输出阶段,commitRoot的实现逻辑是在commitRootImpl函数中,其主要逻辑是处理副作用,执行副作用对应的DOM操作,将最新的 fiber 树结构反映到 DOM 上。除此之外,一些生命周期钩子(比如componentDidXXX)、hook(比如useEffect)需要在commit阶段执行。
commit阶段被分成几个子阶段。对每个阶段的副作用列表做了一个单独的处理,也就是通过调用生命周期函数和hooks进行相应的更新。
commit阶段的主要工作(即Renderer的工作流程)分为三部分:
  • before mutation阶段(执行DOM操作前)
  • mutation阶段(执行DOM操作)
  • layout阶段(执行DOM操作后)
function commitRootImpl(root, renderPriorityLevel) { do { flushPassiveEffects(); } while (rootWithPendingPassiveEffects !== null); flushRenderPhaseStrictModeWarningsInDEV(); const finishedWork = root.finishedWork; const lanes = root.finishedLanes; if (enableSchedulingProfiler) { markCommitStarted(lanes); } if (finishedWork === null) { if (enableSchedulingProfiler) { markCommitStopped(); } return null; } root.finishedWork = null; root.finishedLanes = NoLanes; root.callbackNode = null; let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes); markRootFinished(root, remainingLanes);//离散事件导致的更新处理=》光标 if (rootsWithPendingDiscreteUpdates !== null) { if ( !hasDiscreteLanes(remainingLanes) && rootsWithPendingDiscreteUpdates.has(root) ) { rootsWithPendingDiscreteUpdates.delete(root); } } if (root === workInProgressRoot) { // We can reset these now that they are finished. workInProgressRoot = null; workInProgress = null; workInProgressRootRenderLanes = NoLanes; } else { } // Get the list of effects. 在归阶段的update时,effects链表的形成,只会挂载自己的子Fiber,所以当前应用的根节点的Effect是没有被挂载在这个链表上的 let firstEffect; //如果当前应用的根节点存在Effect,需要将当前应用的根节点挂载在Effect链表的最后 if (finishedWork.flags > PerformedWork) { if (finishedWork.lastEffect !== null) { finishedWork.lastEffect.nextEffect = finishedWork; firstEffect = finishedWork.firstEffect; } else { //如果effect list 不存在,第一项就是根Fiber节点 firstEffect = finishedWork; } } else { // There is no effect on the root. firstEffect = finishedWork.firstEffect; } if (firstEffect !== null) { let previousLanePriority; if (decoupleUpdatePriorityFromScheduler) { previousLanePriority = getCurrentUpdateLanePriority(); setCurrentUpdateLanePriority(SyncLanePriority); } const prevExecutionContext = executionContext; executionContext |= CommitContext; const prevInteractions = pushInteractions(root); ReactCurrentOwner.current = null; focusedInstanceHandle = prepareForCommit(root.containerInfo); shouldFireAfterActiveInstanceBlur = false; nextEffect = firstEffect; //beforemutation执行的工作 do { try { commitBeforeMutationEffects(); } catch (error) { invariant(nextEffect !== null, 'Should be working on an effect.'); captureCommitPhaseError(nextEffect, error); nextEffect = nextEffect.nextEffect; } } while (nextEffect !== null); focusedInstanceHandle = null; if (enableProfilerTimer) { recordCommitTime(); } nextEffect = firstEffect; //mutation阶段执行的工作 do { try { commitMutationEffects(root, renderPriorityLevel); } catch (error) { invariant(nextEffect !== null, 'Should be working on an effect.'); captureCommitPhaseError(nextEffect, error); nextEffect = nextEffect.nextEffect; } } while (nextEffect !== null); if (shouldFireAfterActiveInstanceBlur) { afterActiveInstanceBlur(); } resetAfterCommit(root.containerInfo); 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. nextEffect = firstEffect; //layout阶段的工作 do { try { commitLayoutEffects(root, lanes); } catch (error) { invariant(nextEffect !== null, 'Should be working on an effect.'); captureCommitPhaseError(nextEffect, error); nextEffect = nextEffect.nextEffect; } } while (nextEffect !== null); nextEffect = null; // Tell Scheduler to yield at the end of the frame, so the browser has an // opportunity to paint. requestPaint(); if (enableSchedulerTracing) { popInteractions(((prevInteractions: any): Set<Interaction>)); } executionContext = prevExecutionContext; if (decoupleUpdatePriorityFromScheduler && previousLanePriority != null) { // Reset the priority to the previous non-sync value. setCurrentUpdateLanePriority(previousLanePriority); } } 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. if (enableProfilerTimer) { recordCommitTime(); } } 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. //和本次更新中的useEffect调用有关 rootDoesHavePassiveEffects = false; rootWithPendingPassiveEffects = root; pendingPassiveEffectsLanes = lanes; pendingPassiveEffectsRenderPriority = renderPriorityLevel; } else { // We are done with the effect chain at this point so let's clear the // nextEffect pointers to assist with GC. If we have passive effects, we'll // clear this in flushPassiveEffects. //本次更新不存在useEffect的调用,清理链表,垃圾回收 nextEffect = firstEffect; while (nextEffect !== null) { const nextNextEffect = nextEffect.nextEffect; nextEffect.nextEffect = null; if (nextEffect.flags & Deletion) { detachFiberAfterEffects(nextEffect); } nextEffect = nextNextEffect; } } // Read this again, since an effect might have updated it remainingLanes = root.pendingLanes; // Check if there's remaining work on this root if (remainingLanes !== NoLanes) { if (enableSchedulerTracing) { if (spawnedWorkDuringRender !== null) { const expirationTimes = spawnedWorkDuringRender; spawnedWorkDuringRender = null; for (let i = 0; i < expirationTimes.length; i++) { scheduleInteractions( root, expirationTimes[i], root.memoizedInteractions, ); } } schedulePendingInteractions(root, remainingLanes); } } else { // If there's no remaining work, we can clear the set of already failed // error boundaries. legacyErrorBoundariesThatAlreadyFailed = null; } if (enableSchedulerTracing) { if (!rootDidHavePassiveEffects) { . finishPendingInteractions(root, lanes); } } if (remainingLanes === SyncLane) { //通过计数判断是否是进入了无限循环更新了 if (root === rootWithNestedUpdates) { nestedUpdateCount++; } else { nestedUpdateCount = 0; rootWithNestedUpdates = root; } } else { nestedUpdateCount = 0; } onCommitRootDevTools(finishedWork.stateNode, renderPriorityLevel);//commit可能会出现新的更新,确保在这个根节点上没有新的更新任务 ensureRootIsScheduled(root, now()); if (hasUncaughtError) { hasUncaughtError = false; const error = firstUncaughtError; firstUncaughtError = null; throw error; } if ((executionContext & LegacyUnbatchedContext) !== NoContext) { if (enableSchedulingProfiler) { markCommitStopped(); } return null; } // If layout work was scheduled, flush it now. //如useLayoutEffect中调用setState,这个就是同步的更新,就会在这里面同步执行 flushSyncCallbackQueue(); if (enableSchedulingProfiler) { markCommitStopped(); } return null;}

before mutation阶段

整个过程就是遍历effectList并调用commitBeforeMutationEffects函数处理
function commitBeforeMutationEffects() { while (nextEffect !== null) { const current = nextEffect.alternate;//处理DOM节点渲染/删除后的 autoFocus、blur 逻辑。 if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) { if ((nextEffect.flags & Deletion) !== NoFlags) { if (doesFiberContain(nextEffect, focusedInstanceHandle)) { shouldFireAfterActiveInstanceBlur = true; beforeActiveInstanceBlur(); } } else { // TODO: Move this out of the hot path using a dedicated effect tag. if ( nextEffect.tag === SuspenseComponent && isSuspenseBoundaryBeingHidden(current, nextEffect) && doesFiberContain(nextEffect, focusedInstanceHandle) ) { shouldFireAfterActiveInstanceBlur = true; beforeActiveInstanceBlur(); } } }//调用getSnapshotBeforeUpdate生命周期钩子。 const flags = nextEffect.flags; if ((flags & Snapshot) !== NoFlags) { setCurrentDebugFiberInDEV(nextEffect); commitBeforeMutationEffectOnFiber(current, nextEffect); resetCurrentDebugFiberInDEV(); } //FunctionComponent中useEffect对应的Effect,需要调度passiveeffects的回调函数 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; }}
整体可以分为三部分:
  • 处理DOM节点渲染/删除后的 autoFocus、blur 逻辑。
  • 调用getSnapshotBeforeUpdate生命周期钩子。
  • 调度useEffect。
这里忽略第一部分,看调用getSnapshotBeforeUpdate生命周期钩子的部分

commitBeforeMutationEffects方法调用getSnapshotBeforeUpdate

commitBeforeMutationEffectOnFiber是commitBeforeMutationLifeCycles的别名。
在该方法内会调用getSnapshotBeforeUpdate。
function commitBeforeMutationLifeCycles( current: Fiber | null, finishedWork: Fiber,): void { switch (finishedWork.tag) { case FunctionComponent: case ForwardRef: case SimpleMemoComponent: case Block: { return; } case ClassComponent: { //如果存在 Snapshot 生命周期对应的tag if (finishedWork.flags & Snapshot) { if (current !== null) { const prevProps = current.memoizedProps; const prevState = current.memoizedState; //取到对应的React Component的实例 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.', getComponentName(finishedWork.type) || '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.', getComponentName(finishedWork.type) || '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.', getComponentName(finishedWork.type), ); } } instance.__reactInternalSnapshotBeforeUpdate = snapshot; } } return; } case HostRoot: { if (supportsMutation) { if (finishedWork.flags & Snapshot) { const root = finishedWork.stateNode; clearContainer(root.containerInfo); } } return; } case HostComponent: case HostText: case HostPortal: case IncompleteClassComponent: // Nothing to do for these component types return; } 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.', );}
这个阶段调用getSnapshotBeforeUpdates生命周期,页面没有可见的更新出现。

调度useEffect

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; }); } }
scheduleCallback方法Scheduler模块提供,用于以某个优先级异步调度一个回调函数。 这个函数以一个优先级异步执行回调函数,所以在FunctionComponent中存在useEffect,并且回调函数需要触发的情况,会在beforemutation阶段以normal的优先级调度,由于commit阶段是同步执行的,所以useEffect的回调函数会在commit阶段执行完之后异步执行。
在此处,被异步调度的回调函数就是触发useEffect的方法flushPassiveEffects
function flushPassiveEffects(): boolean { // Returns whether passive effects were flushed. if (pendingPassiveEffectsLanes !== NoLanes) { const priority = higherEventPriority( DefaultEventPriority, lanesToEventPriority(pendingPassiveEffectsLanes), ); const previousPriority = getCurrentUpdatePriority(); try { setCurrentUpdatePriority(priority); return flushPassiveEffectsImpl(); } finally { setCurrentUpdatePriority(previousPriority); } } return false;}
flushPassiveEffects内部会设置优先级,并切调用flushPassiveEffectsImpl
flushPassiveEffectsImpl主要做三件事:
  • 调用该useEffect在上一次render时的销毁函数
  • 调用该useEffect在本次render时的回调函数
  • 如果存在同步任务,不需要等待下次事件循环的宏任务,提前执行他

第一步:执行上一次render时useEffect的销毁函数

useEffect的执行需要保证所有组件useEffect的销毁函数必须都执行完后才能执行任意一个组件的useEffect的回调函数。
这是因为多个组件间可能共用同一个ref。
如果在执行前没有销毁函数,那么在某个组件useEffect的销毁函数中修改的ref.current可能影响另一个组件useEffect的回调函数中的同一个ref的current属性。
useLayoutEffect中也有同样的问题,所以他们都遵循先全部销毁,再执行的顺序。
const unmountEffects = pendingPassiveHookEffectsUnmount; pendingPassiveHookEffectsUnmount = []; for (let i = 0; i < unmountEffects.length; i += 2) { const effect = ((unmountEffects[i]: any): HookEffect); const fiber = ((unmountEffects[i + 1]: any): Fiber); const destroy = effect.destroy; effect.destroy = undefined; if (typeof destroy === 'function') { try { if ( enableProfilerTimer && enableProfilerCommitHooks && fiber.mode & ProfileMode ) { try { startPassiveEffectTimer(); destroy(); } finally { recordPassiveEffectDuration(fiber); } } else { destroy(); } } catch (error) { invariant(fiber !== null, 'Should be working on an effect.'); captureCommitPhaseError(fiber, error); } } }
获取在unmount时处理的销毁函数中其中pendingPassiveHookEffectsUnmount数组的索引i保存需要销毁的effect,i+1保存该effect对应的fiber。(向pendingPassiveHookEffectsUnmount数组内push数据的操作发生在layout阶段 commitLayoutEffectOnFiber方法内部的schedulePassiveEffects方法中。)
function schedulePassiveEffects(finishedWork: Fiber) { 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 { const {next, tag} = effect; if ( (tag & HookPassive) !== NoHookEffect && (tag & HookHasEffect) !== NoHookEffect ) { // 向`pendingPassiveHookEffectsUnmount`数组内`push`要销毁的effect enqueuePendingPassiveHookEffectUnmount(finishedWork, effect); // 向`pendingPassiveHookEffectsMount`数组内`push`要执行回调的effect enqueuePendingPassiveHookEffectMount(finishedWork, effect); } effect = next; } while (effect !== firstEffect); }}

第二步:回调函数的执行

遍历数组,执行useEffect的回调函数
const mountEffects = pendingPassiveHookEffectsMount; pendingPassiveHookEffectsMount = []; for (let i = 0; i < mountEffects.length; i += 2) { const effect = ((mountEffects[i]: any): HookEffect); const fiber = ((mountEffects[i + 1]: any): Fiber); try { const create = effect.create; if ( enableProfilerTimer && enableProfilerCommitHooks && fiber.mode & ProfileMode ) { try { startPassiveEffectTimer(); effect.destroy = create(); } finally { recordPassiveEffectDuration(fiber); } } else { effect.destroy = create(); } } catch (error) { invariant(fiber !== null, 'Should be working on an effect.'); captureCommitPhaseError(fiber, error); } }
其中向pendingPassiveHookEffectsMount中push数据的操作同样发生在schedulePassiveEffects中。
function schedulePassiveEffects(finishedWork: Fiber) { 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 { const {next, tag} = effect; if ( (tag & HookPassive) !== NoHookEffect && (tag & HookHasEffect) !== NoHookEffect ) { enqueuePendingPassiveHookEffectUnmount(finishedWork, effect); enqueuePendingPassiveHookEffectMount(finishedWork, effect); } effect = next; } while (effect !== firstEffect); }}
需要注意的是:与 componentDidMount、componentDidUpdate 不同的是,在浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用。这使得它适用于许多常见的副作用场景,比如设置订阅和事件处理等情况,因此不应在函数中执行阻塞浏览器更新屏幕的操作。
所以useEffect的这两个阶段会在页面渲染后(layout阶段后)异步执行,以防止同步执行阻塞浏览器渲染。

mutation阶段

这个阶段是执行DOM操作的阶段,遍历effectList,执行commitMutationEffects方法。
nextEffect = firstEffect; do { try { commitMutationEffects(root, renderPriorityLevel); } catch (error) { invariant(nextEffect !== null, 'Should be working on an effect.'); captureCommitPhaseError(nextEffect, error); nextEffect = nextEffect.nextEffect; } } while (nextEffect !== null);

概览

这部分同样是遍历effectList,根据 ContentReset effectTag重置文字节点,然后更新ref,最后根据effectTag保存的信息进行对应的插入、更新、删除DOM。
function commitMutationEffects( root: FiberRoot, renderPriorityLevel: ReactPriorityLevel,) { // TODO: Should probably move the bulk of this function to commitWork. while (nextEffect !== null) { setCurrentDebugFiberInDEV(nextEffect); const flags = nextEffect.flags; if (flags & ContentReset) { commitResetTextContent(nextEffect); } if (flags & Ref) { const current = nextEffect.alternate; if (current !== null) { commitDetachRef(current); } if (enableScopeAPI) { // TODO: This is a temporary solution that allowed us to transition away // from React Flare on www. if (nextEffect.tag === ScopeComponent) { commitAttachRef(nextEffect); } } } // The following switch statement is only concerned about placement, // updates, and deletions. To avoid needing to add a case for every possible // bitmap value, we remove the secondary effects from the effect tag and // switch on that value. const primaryFlags = flags & (Placement | Update | Deletion | Hydrating); switch (primaryFlags) { case Placement: { commitPlacement(nextEffect); // Clear the "placement" from effect tag so that we know that this is // inserted, before any life-cycles like componentDidMount gets called. // TODO: findDOMNode doesn't rely on this any more but isMounted does // and isMounted is deprecated anyway so we should be able to kill this. nextEffect.flags &= ~Placement; break; } case PlacementAndUpdate: { // Placement commitPlacement(nextEffect); // Clear the "placement" from effect tag so that we know that this is // inserted, before any life-cycles like componentDidMount gets called. nextEffect.flags &= ~Placement; // Update const current = nextEffect.alternate; commitWork(current, nextEffect); break; } case Hydrating: { nextEffect.flags &= ~Hydrating; break; } case HydratingAndUpdate: { nextEffect.flags &= ~Hydrating; // Update const current = nextEffect.alternate; commitWork(current, nextEffect); break; } case Update: { const current = nextEffect.alternate; commitWork(current, nextEffect); break; } case Deletion: { commitDeletion(root, nextEffect, renderPriorityLevel); break; } } resetCurrentDebugFiberInDEV(); nextEffect = nextEffect.nextEffect; }}
根据以上代码总结,对每个有副作用的Fiber节点执行如下三个操作:
  • 根据ContentReset effectTag重置文字节点
  • 更新ref
  • 根据effectTag分别处理,其中effectTag包括(Placement | Update | Deletion | Hydrating(SSR))

Placement

该方法所做的工作分为三步: 1.获取父级DOM节点。其中finishedWork为传入的Fiber节点
const parentFiber = getHostParentFiber(finishedWork); const parentStateNode = parentFiber.stateNode;
2.获取Fiber节点的DOM兄弟节点
const before = getHostSibling(finishedWork);
3.根据DOM兄弟节点是否存在决定调用parentNode.insertBeforeparentNode.appendChild执行DOM插入操作。
if (isContainer) { insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent); } else { insertOrAppendPlacementNode(finishedWork, before, parent); }
getHostSibling(获取兄弟DOM节点)的执行很耗时,当在同一个父Fiber节点下依次执行多个插入操作,getHostSibling算法的复杂度为指数级。
这是由于Fiber节点不只包括HostComponent,所以Fiber树和渲染的DOM树节点并不是一一对应的。要从Fiber节点找到DOM节点很可能跨层级遍历。
function Item() { return <li><li>;}function App() { return ( <div> <Item/> </div> )}ReactDOM.render(<App/>, document.getElementById('root'));
对应的Fiber树和DOM树结构为:
// Fiber树 child child child childrootFiber -----> App -----> div -----> Item -----> li// DOM树#root ---> div ---> li
当在div的子节点Item前插入一个新节点p,即App变为:
function App() { return ( <div> <p></p> <Item/> </div> )}
对应的Fiber树和DOM树结构为:
// Fiber树 child child childrootFiber -----> App -----> div -----> p | sibling child | -------> Item -----> li// DOM树#root ---> div ---> p | ---> li
此时DOM节点 p的兄弟节点为li,而Fiber节点 p对应的兄弟DOM节点为:
fiberP.sibling.child
fiber p兄弟fiber Item子fiber li

Update

调用的是commitWork
在这里主要关注函数组件和原始类型组件 ### FunctionComponent mutation
调用 commitHookEffectListUnmount()方法,该方法会遍历effectList,执行所有useLayoutEffect hook的销毁函数。
function commitHookEffectListUnmount(tag: number, finishedWork: Fiber) { 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 & tag) === tag) { // Unmount const destroy = effect.destroy; effect.destroy = undefined; if (destroy !== undefined) { destroy(); } } effect = effect.next; } while (effect !== firstEffect); }}

HostComponent mutation

调用的是commitUpdate > commitUpdate->updateProperties->updateDOMProperties
最终会在updateDOMProperties中将render阶段 completeWork (opens new window)中为Fiber节点赋值的updateQueue对应的内容渲染在页面上。
function updateDOMProperties( domElement: Element, updatePayload: Array<any>, wasCustomComponentTag: boolean, isCustomComponentTag: boolean,): void { // TODO: Handle wasCustomComponentTag for (let i = 0; i < updatePayload.length; i += 2) { const propKey = updatePayload[i]; const propValue = updatePayload[i + 1]; if (propKey === STYLE) { // 处理 style setValueForStyles(domElement, propValue); } else if (propKey === DANGEROUSLY_SET_INNER_HTML) { //处理 DANGEROUSLY_SET_INNER_HTML setInnerHTML(domElement, propValue); } else if (propKey === CHILDREN) { // 处理 children setTextContent(domElement, propValue); } else { // 处理剩余 props setValueForProperty(domElement, propKey, propValue, isCustomComponentTag); } }}

Deletion

如果需要删除节点就会执行commitDeletion方法
function commitDeletion( finishedRoot: FiberRoot, current: Fiber, renderPriorityLevel: ReactPriorityLevel,): void { if (supportsMutation) { // Recursively delete all host nodes from the parent. // Detach refs and call componentWillUnmount() on the whole subtree. unmountHostComponents(finishedRoot, current, renderPriorityLevel); } else { // Detach refs and call componentWillUnmount() on the whole subtree. commitNestedUnmounts(finishedRoot, current, renderPriorityLevel); } const alternate = current.alternate; detachFiberMutation(current); if (alternate !== null) { detachFiberMutation(alternate); }}
unmountHostComponents方法中先会获取当前节点的父节点,因为想删除某个节点,需要找到他的父节点
let node: Fiber = current; // Each iteration, currentParent is populated with node's host parent if not // currentParentIsValid. let currentParentIsValid = false; // Note: these two variables *must* always be updated together. let currentParent; let currentParentIsContainer; while (true) { if (!currentParentIsValid) { let parent = node.return; findParent: while (true) { invariant( parent !== null, 'Expected to find a host parent. This error is likely caused by ' + 'a bug in React. Please file an issue.', ); const parentStateNode = parent.stateNode; switch (parent.tag) { case HostComponent: currentParent = parentStateNode; currentParentIsContainer = false; break findParent; case HostRoot: currentParent = parentStateNode.containerInfo; currentParentIsContainer = true; break findParent; case HostPortal: currentParent = parentStateNode.containerInfo; currentParentIsContainer = true; break findParent; case FundamentalComponent: if (enableFundamentalAPI) { currentParent = parentStateNode.instance; currentParentIsContainer = false; } } parent = parent.return; } currentParentIsValid = true; }
找到之后会执行commitNestedUnmounts方法,这个方法嵌套了commitUnmout方法,因为当我们删除一个节点,这个节点可能包含一颗子树,一颗子树中的子孙Fiber节点都需要被递归的删除。
对于FunctionComponent方法,会注册需要被执行的useEffect的回调函数,如果存在useEffect的销毁函数存在,也会执行销毁函数。
对于ClassComponent方法,会调用componentWillUnmount生命周期钩子
综上:

layout阶段

commitLayoutEffects

while (nextEffect !== null) { setCurrentDebugFiberInDEV(nextEffect); const flags = nextEffect.flags; // 调用生命周期钩子和hook if (flags & (Update | Callback)) { const current = nextEffect.alternate; commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes); } if (enableScopeAPI) { // 赋值ref if (flags & Ref && nextEffect.tag !== ScopeComponent) { commitAttachRef(nextEffect); } } else { if (flags & Ref) { commitAttachRef(nextEffect); } } resetCurrentDebugFiberInDEV(); nextEffect = nextEffect.nextEffect; }
这里的commitLayoutEffectOnFiber是commitLifeCycles方法
与前两个阶段类似,layout阶段也是遍历effectList,最终执行 commitLayoutEffectOnFiber方法
  • 触发状态更新的this.setState的第二个参数回调函数,也会在此时调用
  • 对于FunctionComponent及ForwardRef、React.memo包裹的FunctionComponent,他在这里会调用useLayoutEffect hook的回调函数,因为参数是HookLayout | HookHasEffect,所以只处理由useLayoutEffect()创建的effect,调用effect.create()之后, 将返回值赋值到effect.destroy,并且添加useEffect的销毁函数和回调函数到队列
  • 对于HostRoot,即rootFiber,如果赋值了第三个参数回调函数,也会在此时调用

commitAttachRef

获取DOM实例,更新ref
function commitAttachRef(finishedWork: Fiber) { const ref = finishedWork.ref; if (ref !== null) { const instance = finishedWork.stateNode; let instanceToUse; switch (finishedWork.tag) { case HostComponent: instanceToUse = getPublicInstance(instance); break; default: instanceToUse = instance; } // Moved outside to ensure DCE works with this flag if (enableScopeAPI && finishedWork.tag === ScopeComponent) { instanceToUse = instance; } if (typeof ref === 'function') { ref(instanceToUse); } else { if (__DEV__) { if (!ref.hasOwnProperty('current')) { console.error( 'Unexpected ref object provided for %s. ' + 'Use either a ref-setter function or React.createRef().', getComponentName(finishedWork.type), ); } } ref.current = instanceToUse; } }}

current Fiber树切换

root.current = finishedWork;
标志着layout阶段结束。
componentWillUnmount会在mutation阶段执行,此时current Fiber树还指向前一次更新的Fiber树,在生命周期钩子内获取的DOM还是更新前的。
componentDidMountcomponentDidUpdate会在layout阶段执行。此时current Fiber树已经指向更新后Fiber树,在生命周期钩子内获取的DOM就是更新后的。