Renderer工作的阶段在React内部被称为commit 阶段。
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-fallthrough
case 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.');
}
}
}
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;
}
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 setTimeout
pendingPassiveTransitions = 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 lifecycles
ReactCurrentOwner$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 it
remainingLanes = 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 it
remainingLanes = 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;
}