commit阶段流程图

一、finishConcurrentRender函数
function finishConcurrentRender(root, exitStatus, lanes) {switch (exitStatus) {case RootInProgress:case RootFatalErrored:{throw new Error('Root did not complete. This is a bug in React.');}// Flow knows about invariant, so it complains if I add a break// statement, but eslint doesn't know about invariant, so it complains// if I do. eslint-disable-next-line no-fallthroughcase RootErrored:{// We should have already attempted to retry this tree. If we reached// this point, it errored again. Commit it.commitRoot(root, workInProgressRootRecoverableErrors, workInProgressTransitions);break;}case RootSuspended:{markRootSuspended$1(root, lanes); // We have an acceptable loading state. We need to figure out if we// should immediately commit it or wait a bit.if (includesOnlyRetries(lanes) && // do not delay if we're inside an act() scope!shouldForceFlushFallbacksInDEV()) {// This render only included retries, no updates. Throttle committing// retries so that we don't show too many loading states too quickly.var msUntilTimeout = globalMostRecentFallbackTime + FALLBACK_THROTTLE_MS - now(); // Don't bother with a very short suspense time.if (msUntilTimeout > 10) {var nextLanes = getNextLanes(root, NoLanes);if (nextLanes !== NoLanes) {// There's additional work on this root.break;}var suspendedLanes = root.suspendedLanes;if (!isSubsetOfLanes(suspendedLanes, lanes)) {// We should prefer to render the fallback of at the last// suspended level. Ping the last suspended level to try// rendering it again.// FIXME: What if the suspended lanes are Idle? Should not restart.var eventTime = requestEventTime();markRootPinged(root, suspendedLanes);break;} // The render is suspended, it hasn't timed out, and there's no// lower priority work to do. Instead of committing the fallback// immediately, wait for more data to arrive.root.timeoutHandle = scheduleTimeout(commitRoot.bind(null, root, workInProgressRootRecoverableErrors, workInProgressTransitions), msUntilTimeout);break;}} // The work expired. Commit immediately.commitRoot(root, workInProgressRootRecoverableErrors, workInProgressTransitions);break;}case RootSuspendedWithDelay:{markRootSuspended$1(root, lanes);if (includesOnlyTransitions(lanes)) {// This is a transition, so we should exit without committing a// placeholder and without scheduling a timeout. Delay indefinitely// until we receive more data.break;}if (!shouldForceFlushFallbacksInDEV()) {// This is not a transition, but we did trigger an avoided state.// Schedule a placeholder to display after a short delay, using the Just// Noticeable Difference.// TODO: Is the JND optimization worth the added complexity? If this is// the only reason we track the event time, then probably not.// Consider removing.var mostRecentEventTime = getMostRecentEventTime(root, lanes);var eventTimeMs = mostRecentEventTime;var timeElapsedMs = now() - eventTimeMs;var _msUntilTimeout = jnd(timeElapsedMs) - timeElapsedMs; // Don't bother with a very short suspense time.if (_msUntilTimeout > 10) {// Instead of committing the fallback immediately, wait for more data// to arrive.root.timeoutHandle = scheduleTimeout(commitRoot.bind(null, root, workInProgressRootRecoverableErrors, workInProgressTransitions), _msUntilTimeout);break;}} // Commit the placeholder.commitRoot(root, workInProgressRootRecoverableErrors, workInProgressTransitions);break;}case RootCompleted:{// The work completed. Ready to commit.commitRoot(root, workInProgressRootRecoverableErrors, workInProgressTransitions);break;}default:{throw new Error('Unknown root exit status.');}}
}
二、commitRoot函数
function commitRoot(root, recoverableErrors, transitions) {// TODO: This no longer makes any sense. We already wrap the mutation and// layout phases. Should be able to remove.var previousUpdateLanePriority = getCurrentUpdatePriority();var prevTransition = ReactCurrentBatchConfig$3.transition;try {ReactCurrentBatchConfig$3.transition = null;setCurrentUpdatePriority(DiscreteEventPriority);commitRootImpl(root, recoverableErrors, transitions, previousUpdateLanePriority);} finally {ReactCurrentBatchConfig$3.transition = prevTransition;setCurrentUpdatePriority(previousUpdateLanePriority);}return null;
}
三、commitRootImpl函数
function commitRootImpl(root, recoverableErrors, transitions, renderPriorityLevel) {do {// `flushPassiveEffects` will call `flushSyncUpdateQueue` at the end, which// means `flushPassiveEffects` will sometimes result in additional// passive effects. So we need to keep flushing in a loop until there are// no more pending effects.// TODO: Might be better if `flushPassiveEffects` did not automatically// flush synchronous work at the end, to avoid factoring hazards like this.flushPassiveEffects();} while (rootWithPendingPassiveEffects !== null);flushRenderPhaseStrictModeWarningsInDEV();if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {throw new Error('Should not already be working.');}var finishedWork = root.finishedWork;var lanes = root.finishedLanes;{markCommitStarted(lanes);}if (finishedWork === null) {{markCommitStopped();}return null;} else {{if (lanes === NoLanes) {error('root.finishedLanes should not be empty during a commit. This is a ' + 'bug in React.');}}}root.finishedWork = null;root.finishedLanes = NoLanes;if (finishedWork === root.current) {throw new Error('Cannot commit the same tree as before. This error is likely caused by ' + 'a bug in React. Please file an issue.');} // commitRoot never returns a continuation; it always finishes synchronously.// So we can clear these now to allow a new callback to be scheduled.root.callbackNode = null;root.callbackPriority = NoLane; // Update the first and last pending times on this root. The new first// pending time is whatever is left on the root fiber.var remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);markRootFinished(root, remainingLanes);if (root === workInProgressRoot) {// We can reset these now that they are finished.workInProgressRoot = null;workInProgress = null;workInProgressRootRenderLanes = NoLanes;} // If there are pending passive effects, schedule a callback to process them.// Do this as early as possible, so it is queued before anything else that// might get scheduled in the commit phase. (See #16714.)// TODO: Delete all other places that schedule the passive effect callback// They're redundant.if ((finishedWork.subtreeFlags & PassiveMask) !== NoFlags || (finishedWork.flags & PassiveMask) !== NoFlags) {if (!rootDoesHavePassiveEffects) {rootDoesHavePassiveEffects = true;// to store it in pendingPassiveTransitions until they get processed// We need to pass this through as an argument to commitRoot// because workInProgressTransitions might have changed between// the previous render and commit if we throttle the commit// with setTimeoutpendingPassiveTransitions = transitions;scheduleCallback$1(NormalPriority, function () {flushPassiveEffects(); // This render triggered passive effects: release the root cache pool// *after* passive effects fire to avoid freeing a cache pool that may// be referenced by a node in the tree (HostRoot, Cache boundary etc)return null;});}} // Check if there are any effects in the whole tree.// TODO: This is left over from the effect list implementation, where we had// to check for the existence of `firstEffect` to satisfy Flow. I think the// only other reason this optimization exists is because it affects profiling.// Reconsider whether this is necessary.var subtreeHasEffects = (finishedWork.subtreeFlags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags;var rootHasEffect = (finishedWork.flags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags;if (subtreeHasEffects || rootHasEffect) {var prevTransition = ReactCurrentBatchConfig$3.transition;ReactCurrentBatchConfig$3.transition = null;var previousPriority = getCurrentUpdatePriority();setCurrentUpdatePriority(DiscreteEventPriority);var prevExecutionContext = executionContext;executionContext |= CommitContext; // Reset this to null before calling lifecyclesReactCurrentOwner$2.current = null; // The commit phase is broken into several sub-phases. We do a separate pass// of the effect list for each phase: all mutation effects come before all// layout effects, and so on.// The first phase a "before mutation" phase. We use this phase to read the// state of the host tree right before we mutate it. This is where// getSnapshotBeforeUpdate is called.var shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(root, finishedWork);{// Mark the current commit time to be shared by all Profilers in this// batch. This enables them to be grouped later.recordCommitTime();}commitMutationEffects(root, finishedWork, lanes);resetAfterCommit(root.containerInfo); // The work-in-progress tree is now the current tree. This must come after// the mutation phase, so that the previous tree is still current during// componentWillUnmount, but before the layout phase, so that the finished// work is current during componentDidMount/Update.root.current = finishedWork; // The next phase is the layout phase, where we call effects that read{markLayoutEffectsStarted(lanes);}commitLayoutEffects(finishedWork, root, lanes);{markLayoutEffectsStopped();}// opportunity to paint.requestPaint();executionContext = prevExecutionContext; // Reset the priority to the previous non-sync value.setCurrentUpdatePriority(previousPriority);ReactCurrentBatchConfig$3.transition = prevTransition;} 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.{recordCommitTime();}}var 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.rootDoesHavePassiveEffects = false;rootWithPendingPassiveEffects = root;pendingPassiveEffectsLanes = lanes;} else {{nestedPassiveUpdateCount = 0;rootWithPassiveNestedUpdates = null;}} // Read this again, since an effect might have updated itremainingLanes = root.pendingLanes; // Check if there's remaining work on this root// TODO: This is part of the `componentDidCatch` implementation. Its purpose// is to detect whether something might have called setState inside// `componentDidCatch`. The mechanism is known to be flawed because `setState`// inside `componentDidCatch` is itself flawed — that's why we recommend// `getDerivedStateFromError` instead. However, it could be improved by// checking if remainingLanes includes Sync work, instead of whether there's// any work remaining at all (which would also include stuff like Suspense// retries or transitions). It's been like this for a while, though, so fixing// it probably isn't that urgent.if (remainingLanes === NoLanes) {// If there's no remaining work, we can clear the set of already failed// error boundaries.legacyErrorBoundariesThatAlreadyFailed = null;}{if (!rootDidHavePassiveEffects) {commitDoubleInvokeEffectsInDEV(root.current, false);}}onCommitRoot(finishedWork.stateNode, renderPriorityLevel);{if (isDevToolsPresent) {root.memoizedUpdaters.clear();}}{onCommitRoot$1();} // Always call this before exiting `commitRoot`, to ensure that any// additional work on this root is scheduled.ensureRootIsScheduled(root, now());if (recoverableErrors !== null) {// There were errors during this render, but recovered from them without// needing to surface it to the UI. We log them here.var onRecoverableError = root.onRecoverableError;for (var i = 0; i < recoverableErrors.length; i++) {var recoverableError = recoverableErrors[i];var componentStack = recoverableError.stack;var digest = recoverableError.digest;onRecoverableError(recoverableError.value, {componentStack: componentStack,digest: digest});}}if (hasUncaughtError) {hasUncaughtError = false;var error$1 = firstUncaughtError;firstUncaughtError = null;throw error$1;} // If the passive effects are the result of a discrete render, flush them// synchronously at the end of the current task so that the result is// immediately observable. Otherwise, we assume that they are not// order-dependent and do not need to be observed by external systems, so we// can wait until after paint.// TODO: We can optimize this by not scheduling the callback earlier. Since we// currently schedule the callback in multiple places, will wait until those// are consolidated.if (includesSomeLane(pendingPassiveEffectsLanes, SyncLane) && root.tag !== LegacyRoot) {flushPassiveEffects();} // Read this again, since a passive effect might have updated itremainingLanes = root.pendingLanes;if (includesSomeLane(remainingLanes, SyncLane)) {{markNestedUpdateScheduled();} // Count the number of times the root synchronously re-renders without// finishing. If there are too many, it indicates an infinite update loop.if (root === rootWithNestedUpdates) {nestedUpdateCount++;} else {nestedUpdateCount = 0;rootWithNestedUpdates = root;}} else {nestedUpdateCount = 0;} // If layout work was scheduled, flush it now.flushSyncCallbacks();{markCommitStopped();}return null;
}