- commitRootImpl(root, renderPriorityLevel)的源码">commitRootImpl(root, renderPriorityLevel)的源码
- before mutation阶段
- commitBeforeMutationEffects方法调用getSnapshotBeforeUpdate">commitBeforeMutationEffects方法调用getSnapshotBeforeUpdate
- 调度useEffect">调度useEffect
- mutation阶段
- layout阶段
在commitWork前,会将在workloopSync中生成的workInProgressfiber树赋值给fiberRoot的finishedWork属性。
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 itremainingLanes = root.pendingLanes;// Check if there's remaining work on this rootif (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 生命周期对应的tagif (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 typesreturn;}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`要销毁的effectenqueuePendingPassiveHookEffectUnmount(finishedWork, effect);// 向`pendingPassiveHookEffectsMount`数组内`push`要执行回调的effectenqueuePendingPassiveHookEffectMount(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: {// PlacementcommitPlacement(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;// Updateconst current = nextEffect.alternate;commitWork(current, nextEffect);break;}case Hydrating: {nextEffect.flags &= ~Hydrating;break;}case HydratingAndUpdate: {nextEffect.flags &= ~Hydrating;// Updateconst 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.insertBefore或parentNode.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) {// Unmountconst 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 wasCustomComponentTagfor (let i = 0; i < updatePayload.length; i += 2) {const propKey = updatePayload[i];const propValue = updatePayload[i + 1];if (propKey === STYLE) {// 处理 stylesetValueForStyles(domElement, propValue);} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {//处理 DANGEROUSLY_SET_INNER_HTMLsetInnerHTML(domElement, propValue);} else if (propKey === CHILDREN) {// 处理 childrensetTextContent(domElement, propValue);} else {// 处理剩余 propssetValueForProperty(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生命周期钩子
综上:
- 递归调用Fiber节点及其
子孙Fiber节点中fiber.tag为ClassComponent的componentWillUnmount生命周期钩子,从页面移除Fiber节点对应DOM节点 - 解绑ref
- 调度useEffect的销毁函数,向
pendingPassiveHookEffectsUnmount数组内push要销毁的effect
layout阶段
commitLayoutEffects
while (nextEffect !== null) {setCurrentDebugFiberInDEV(nextEffect);const flags = nextEffect.flags;// 调用生命周期钩子和hookif (flags & (Update | Callback)) {const current = nextEffect.alternate;commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);}if (enableScopeAPI) {// 赋值refif (flags & Ref && nextEffect.tag !== ScopeComponent) {commitAttachRef(nextEffect);}} else {if (flags & Ref) {commitAttachRef(nextEffect);}}resetCurrentDebugFiberInDEV();nextEffect = nextEffect.nextEffect;}
这里的commitLayoutEffectOnFiber是commitLifeCycles方法
与前两个阶段类似,layout阶段也是遍历effectList,最终执行 commitLayoutEffectOnFiber方法
- 对于ClassComponent,他会通过current === null?区分是mount还是update,调用componentDidMount或 componentDidUpdate
- 触发状态更新的
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 flagif (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还是更新前的。
componentDidMount和componentDidUpdate会在layout阶段执行。此时current Fiber树已经指向更新后的Fiber树,在生命周期钩子内获取的DOM就是更新后的。
