在输出阶段,commitRoot的实现逻辑是在commitRootImpl函数中,其主要逻辑是处理副作用,执行副作用对应的DOM操作,将最新的 fiber 树结构反映到 DOM 上。除此之外,一些生命周期钩子(比如componentDidXXX)、hook(比如useEffect)需要在commit阶段执行。
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); }
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(); } }
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); } }
functioncommitBeforeMutationLifeCycles( 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.', ); }
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(); returnnull; }); } }
functioncommitMutationEffects( 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; } }
functioncommitAttachRef(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), ); } }