performWorkOnRoot 中的 renderRoot 生成 React Tree Reconciliation,completeRoot 则是 commit 阶段。
例如,同步更新代码如下:
function performWorkOnRoot(root: FiberRoot,expirationTime: ExpirationTime,isYieldy: boolean,) {//...renderRoot(root, isYieldy);finishedWork = root.finishedWork;if (finishedWork !== null) {// We've completed the root. Commit it.completeRoot(root, finishedWork, expirationTime);}//...}
renderRoot之后,就开始completeRoot。此时的执行栈为:
commitRoot进入提交阶段。在completeRoot中可以阻止 commit 提交!
function completeRoot(root: FiberRoot,finishedWork: Fiber, expirationTime: ExpirationTime): void {const firstBatch = root.firstBatch;if (firstBatch !== null && firstBatch._expirationTime >= expirationTime) {if (completedBatches === null) {completedBatches = [firstBatch];} else {completedBatches.push(firstBatch);}if (firstBatch._defer) {// 阻止提交root.finishedWork = finishedWork;root.expirationTime = NoWork;return;}}root.finishedWork = null;if (root === lastCommittedRootDuringThisBatch) {//嵌套循环计数nestedUpdateCount++;} else {lastCommittedRootDuringThisBatch = root;nestedUpdateCount = 0;}commitRoot(root, finishedWork);}
commitRoot
commitRoot分为commitBeforeMutationLifecycles 、commitAllHostEffects 、commitAllLifeCycles 三个阶段。
function commitRoot(root: FiberRoot, finishedWork: Fiber): void {isWorking = true;isCommitting = true;startCommitTimer();const committedExpirationTime = root.pendingCommitExpirationTime;root.pendingCommitExpirationTime = NoWork;const updateExpirationTimeBeforeCommit = finishedWork.expirationTime;const childExpirationTimeBeforeCommit = finishedWork.childExpirationTime;const earliestRemainingTimeBeforeCommit =childExpirationTimeBeforeCommit > updateExpirationTimeBeforeCommit? childExpirationTimeBeforeCommit : updateExpirationTimeBeforeCommit;markCommittedPriorityLevels(root, earliestRemainingTimeBeforeCommit);let prevInteractions: Set<Interaction> = (null: any);if (enableSchedulerTracing) {prevInteractions = __interactionsRef.current;__interactionsRef.current = root.memoizedInteractions;}ReactCurrentOwner.current = null;let firstEffect;if (finishedWork.effectTag > PerformedWork) {if (finishedWork.lastEffect !== null) {finishedWork.lastEffect.nextEffect = finishedWork;firstEffect = finishedWork.firstEffect;} else {firstEffect = finishedWork;}} else {firstEffect = finishedWork.firstEffect;}prepareForCommit(root.containerInfo);// Invoke instances of getSnapshotBeforeUpdate before mutation.nextEffect = firstEffect;startCommitSnapshotEffectsTimer();while (nextEffect !== null) {let didError = false;let error;try {commitBeforeMutationLifecycles();} catch (e) {didError = true;error = e;}if (didError) {captureCommitPhaseError(nextEffect, error);if (nextEffect !== null) {// Clean-upnextEffect = nextEffect.nextEffect;}}}stopCommitSnapshotEffectsTimer();if (enableProfilerTimer) {recordCommitTime();}nextEffect = firstEffect;startCommitHostEffectsTimer();while (nextEffect !== null) {let didError = false;let error;try {commitAllHostEffects();} catch (e) {didError = true;error = e;}if (didError) {captureCommitPhaseError(nextEffect, error);if (nextEffect !== null) {// Clean-upnextEffect = nextEffect.nextEffect;}}}stopCommitHostEffectsTimer();resetAfterCommit(root.containerInfo);root.current = finishedWork;nextEffect = firstEffect;startCommitLifeCyclesTimer();while (nextEffect !== null) {let didError = false;let error;try {commitAllLifeCycles(root, committedExpirationTime);} catch (e) {didError = true;error = e;}if (didError) {captureCommitPhaseError(nextEffect, error);if (nextEffect !== null) {nextEffect = nextEffect.nextEffect;}}}isCommitting = false;isWorking = false;stopCommitLifeCyclesTimer();stopCommitTimer();onCommitRoot(finishedWork.stateNode);const updateExpirationTimeAfterCommit = finishedWork.expirationTime;const childExpirationTimeAfterCommit = finishedWork.childExpirationTime;const earliestRemainingTimeAfterCommit =childExpirationTimeAfterCommit > updateExpirationTimeAfterCommit? childExpirationTimeAfterCommit: updateExpirationTimeAfterCommit;if (earliestRemainingTimeAfterCommit === NoWork) {legacyErrorBoundariesThatAlreadyFailed = null;}onCommit(root, earliestRemainingTimeAfterCommit);//清空值//....}
执行这三个阶段使用的都是effect链,只做对有此效果的组件进行更新。
commitBeforeMutationLifecycles
commitBeforeMutationLifecycles 执行在mutation 之前的getSnapshotBeforeUpdate 生命周期方法;
//packages\react-reconciler\src\ReactFiberScheduler.jsfunction commitBeforeMutationLifecycles() {//结尾是 Lifecycleswhile (nextEffect !== null) {const effectTag = nextEffect.effectTag;if (effectTag & Snapshot) {//检验效果recordEffect();const current = nextEffect.alternate;commitBeforeMutationLifeCycles(current, nextEffect);// 结尾是Lifecycles}nextEffect = nextEffect.nextEffect;}}//名字相同,大小写不同function commitBeforeMutationLifeCycles(current: Fiber | null,finishedWork: Fiber): void {switch (finishedWork.tag) {case FunctionComponent:case ForwardRef:case SimpleMemoComponent: {commitHookEffectList(UnmountSnapshot, NoHookEffect, finishedWork);return;}case ClassComponent: {if (finishedWork.effectTag & Snapshot) {if (current !== null) {const prevProps = current.memoizedProps;const prevState = current.memoizedState;startPhaseTimer(finishedWork, 'getSnapshotBeforeUpdate');const instance = finishedWork.stateNode;const snapshot = instance.getSnapshotBeforeUpdate(finishedWork.elementType === finishedWork.type? prevProps : resolveDefaultProps(finishedWork.type, prevProps),prevState,);instance.__reactInternalSnapshotBeforeUpdate = snapshot;stopPhaseTimer();}}return;}case HostRoot:case HostComponent:case HostText:case HostPortal:case IncompleteClassComponent:// Nothing to do for these component typesreturn;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.',);}}}
commitAllHostEffects
commitAllHostEffects 中将 staticNode 添加到容器中;
function commitAllHostEffects() {while (nextEffect !== null) {recordEffect();const effectTag = nextEffect.effectTag;if (effectTag & ContentReset) {commitResetTextContent(nextEffect);//清空文本}if (effectTag & Ref) {const current = nextEffect.alternate;if (current !== null) {commitDetachRef(current);}}let primaryEffectTag = effectTag & (Placement | Update | Deletion);switch (primaryEffectTag) {case Placement: {commitPlacement(nextEffect);//挂载nextEffect.effectTag &= ~Placement;break;}case PlacementAndUpdate: {commitPlacement(nextEffect);nextEffect.effectTag &= ~Placement;const current = nextEffect.alternate;commitWork(current, nextEffect);break;}case Update: {const current = nextEffect.alternate;commitWork(current, nextEffect);break;}case Deletion: {commitDeletion(nextEffect);break;}}nextEffect = nextEffect.nextEffect;}}function commitDetachRef(current: Fiber) {const currentRef = current.ref;if (currentRef !== null) {if (typeof currentRef === 'function') {currentRef(null);} else {currentRef.current = null;}}}
commitPlacement
在 commitPlacement 中查找父元素,并将子元素追加到容器中。
function commitPlacement(finishedWork: Fiber): void {if (!supportsMutation) { return; }//递归地将所有主机节点插入父节点const parentFiber = getHostParentFiber(finishedWork);// 注意:这两个变量*必须*总是一起更新let parent;let isContainer;switch (parentFiber.tag) {case HostComponent:parent = parentFiber.stateNode;isContainer = false;break;case HostRoot:parent = parentFiber.stateNode.containerInfo;isContainer = true;break;case HostPortal:parent = parentFiber.stateNode.containerInfo;isContainer = true;break;default:invariant(false, 'Invalid host parent fiber. This error is likely caused by a bug ' +'in React. Please file an issue.' );}if (parentFiber.effectTag & ContentReset) {// 在执行任何插入之前重置父类的文本内容resetTextContent(parent);// 从效果标签中清除ContentResetparentFiber.effectTag &= ~ContentReset;}const before = getHostSibling(finishedWork);let node: Fiber = finishedWork;while (true) {if (node.tag === HostComponent || node.tag === HostText) {if (before) {if (isContainer) {insertInContainerBefore(parent, node.stateNode, before);} else {insertBefore(parent, node.stateNode, before);}} else {if (isContainer) {// 将子节点追加到容器中appendChildToContainer(parent, node.stateNode);} else {appendChild(parent, node.stateNode);}}} else if (node.tag === HostPortal) {} else if (node.child !== null) {node.child.return = node;node = node.child;continue;}if (node === finishedWork) { return; }while (node.sibling === null) {if (node.return === null || node.return === finishedWork) { return; }node = node.return;}node.sibling.return = node.return;node = node.sibling;}}
在commitPlacement 中实现第一次渲染:浏览器中可见到dom树。
commitWork
最主要更新的是HostComponent、HostText、SuspenseComponent。HostComponent 更新时会参照UpdatePayload(finishedWork.updateQueue) 来进行更新。
function commitWork(current: Fiber | null, finishedWork: Fiber): void {if (!supportsMutation) {switch (finishedWork.tag) {case FunctionComponent:case ForwardRef:case MemoComponent:case SimpleMemoComponent: {commitHookEffectList(UnmountMutation, MountMutation, finishedWork);return;}}commitContainer(finishedWork);return;}switch (finishedWork.tag) {case FunctionComponent:case ForwardRef:case MemoComponent:case SimpleMemoComponent: {//注意:我们目前从不使用MountMutation,但是useLayout使用UnmountMutation.commitHookEffectList(UnmountMutation, MountMutation, finishedWork);return;}case ClassComponent: {return; }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: 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;const oldText: string = current !== null ? current.memoizedProps : newText;commitTextUpdate(textInstance, oldText, newText);return;}case HostRoot: { return; }case Profiler: { return; }case SuspenseComponent: {let newState: SuspenseState | null = finishedWork.memoizedState;let newDidTimeout;let primaryChildParent = finishedWork;if (newState === null) {newDidTimeout = false;} else {newDidTimeout = true;primaryChildParent = finishedWork.child;if (newState.timedOutAt === NoWork) {newState.timedOutAt = requestCurrentTime();}}if (primaryChildParent !== null) {hideOrUnhideAllChildren(primaryChildParent, newDidTimeout);}// 如果这个边界刚好超时,那么它将有一组thenable。// 对于每个thenable,附加一个侦听器,以便当它解析时,React尝试以主(预超时)状态重新呈现边界。const thenables: Set<Thenable> | null = (finishedWork.updateQueue: any);if (thenables !== null) {finishedWork.updateQueue = null;let retryCache = finishedWork.stateNode;if (retryCache === null) {retryCache = finishedWork.stateNode = new PossiblyWeakSet();}thenables.forEach(thenable => {// Memoize using the boundary fiber to prevent redundant listeners.let retry = retryTimedOutBoundary.bind(null, finishedWork, thenable);if (enableSchedulerTracing) {retry = Schedule_tracing_wrap(retry);}if (!retryCache.has(thenable)) {retryCache.add(thenable);thenable.then(retry, retry);}});}return;}case IncompleteClassComponent: {return;}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.',);}}}
react会保留dom对象的引用,更新HostComponent、HostText 时直接使用对应的引用,完成具体 dom 或文本的更新。commitAllHostEffects 在一个大的循环中进行的,更新所有的元素。
commitAllLifeCycles
挂载或更新完成后,进入到commitAllLifeCycles 阶段。commitAllLifeCycles 执行componentDidMount或componentDidUpdate。
function commitAllLifeCycles(finishedRoot: FiberRoot,committedExpirationTime: ExpirationTime) {while (nextEffect !== null) {const effectTag = nextEffect.effectTag;if (effectTag & (Update | Callback)) {recordEffect();const current = nextEffect.alternate;commitLifeCycles(finishedRoot,current,nextEffect,committedExpirationTime);}if (effectTag & Ref) {recordEffect();commitAttachRef(nextEffect);}if (enableHooks && effectTag & Passive) {rootWithPendingPassiveEffects = finishedRoot;}nextEffect = nextEffect.nextEffect;}}
commitLifeCycles
执行相应组件的生命周期。
function commitLifeCycles(finishedRoot: FiberRoot, current: Fiber | null,finishedWork: Fiber,committedExpirationTime: ExpirationTime,): void {switch (finishedWork.tag) {case FunctionComponent:case ForwardRef:case SimpleMemoComponent: {commitHookEffectList(UnmountLayout, MountLayout, finishedWork);break;}case ClassComponent: {const instance = finishedWork.stateNode;if (finishedWork.effectTag & Update) {if (current === null) {startPhaseTimer(finishedWork, 'componentDidMount');instance.componentDidMount();stopPhaseTimer();} else {const prevProps =finishedWork.elementType === finishedWork.type? current.memoizedProps: resolveDefaultProps(finishedWork.type, current.memoizedProps);const prevState = current.memoizedState;startPhaseTimer(finishedWork, 'componentDidUpdate');instance.componentDidUpdate(prevProps,prevState,instance.__reactInternalSnapshotBeforeUpdate);stopPhaseTimer();}}const updateQueue = finishedWork.updateQueue;if (updateQueue !== null) {commitUpdateQueue(finishedWork,updateQueue,instance,committedExpirationTime);}return;}case HostRoot: {const updateQueue = finishedWork.updateQueue;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,committedExpirationTime);}return;}case HostComponent: {const instance: Instance = finishedWork.stateNode;if (current === null && finishedWork.effectTag & Update) {const type = finishedWork.type;const props = finishedWork.memoizedProps;commitMount(instance, type, props, finishedWork);}return;}case HostText: {return;}case HostPortal: {return;}case Profiler: {if (enableProfilerTimer) {const onRender = finishedWork.memoizedProps.onRender;if (enableSchedulerTracing) {onRender(finishedWork.memoizedProps.id,current === null ? 'mount' : 'update',finishedWork.actualDuration,finishedWork.treeBaseDuration,finishedWork.actualStartTime,getCommitTime(),finishedRoot.memoizedInteractions,);} else {onRender(finishedWork.memoizedProps.id,current === null ? 'mount' : 'update',finishedWork.actualDuration,finishedWork.treeBaseDuration,finishedWork.actualStartTime,getCommitTime(),);}}return;}case SuspenseComponent:break;case IncompleteClassComponent: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.',);}}}
我们常用的是ClassComponent中componentDidMount、componentDidUpdate 生命周期,如果在组件生命周期中调用setState,则会触发二次渲染。至此组件挂载和生命周期基本完成。
