// Except for NoPriority, these correspond to Scheduler priorities. We use // ascending numbers so we can compare them like numbers. They start at 90 to // avoid clashing with Scheduler's priorities. exportconst ImmediatePriority: ReactPriorityLevel = 99; exportconst UserBlockingPriority: ReactPriorityLevel = 98; exportconst NormalPriority: ReactPriorityLevel = 97; exportconst LowPriority: ReactPriorityLevel = 96; exportconst IdlePriority: ReactPriorityLevel = 95; // NoPriority is the absence of priority. Also React-only. exportconst NoPriority: ReactPriorityLevel = 90;
functionunstable_runWithPriority(priorityLevel, eventHandler) { switch (priorityLevel) { case ImmediatePriority: case UserBlockingPriority: case NormalPriority: case LowPriority: case IdlePriority: break; default: priorityLevel = NormalPriority; }
var previousPriorityLevel = currentPriorityLevel; currentPriorityLevel = priorityLevel;
functioncommitRootImpl(root, 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();
invariant( (executionContext & (RenderContext | CommitContext)) === NoContext, 'Should not already be working.', );
invariant( finishedWork !== root.current, '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.callbackExpirationTime = NoWork; root.callbackPriority_old = NoPriority;
// Update the first and last pending times on this root. The new first // pending time is whatever is left on the root fiber. const remainingExpirationTimeBeforeCommit = getRemainingExpirationTime( finishedWork, ); markRootFinishedAtTime( root, expirationTime, remainingExpirationTimeBeforeCommit, );
// Clear already finished discrete updates in case that a later call of // `flushDiscreteUpdates` starts a useless render pass which may cancels // a scheduled timeout. if (rootsWithPendingDiscreteUpdates !== null) { const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root); if ( lastDiscreteTime !== undefined && remainingExpirationTimeBeforeCommit < lastDiscreteTime ) { rootsWithPendingDiscreteUpdates.delete(root); } }
if (root === workInProgressRoot) { // We can reset these now that they are finished. workInProgressRoot = null; workInProgress = null; renderExpirationTime = NoWork; } else { // This indicates that the last root we worked on is not the same one that // we're committing now. This most commonly happens when a suspended root // times out. }
// Get the list of effects. let firstEffect; if (finishedWork.effectTag > PerformedWork) { // 256 > 1 // A fiber's effect list consists only of its children, not itself. So if // the root has an effect, we need to add it to the end of the list. The // resulting list is the set that would belong to the root's parent, if it // had one; that is, all the effects in the tree including the root. if (finishedWork.lastEffect !== null) { finishedWork.lastEffect.nextEffect = finishedWork; firstEffect = finishedWork.firstEffect; } else { firstEffect = finishedWork; } } else { // There is no effect on the root. firstEffect = finishedWork.firstEffect; }
// Reset this to null before calling lifecycles ReactCurrentOwner.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. focusedInstanceHandle = prepareForCommit(root.containerInfo); shouldFireAfterActiveInstanceBlur = false;
nextEffect = firstEffect; // 在此处设置了nextEffect do { try { // 感觉首次渲染,上面的代码并没有做什么有重大影响的操作 commitBeforeMutationEffects(); } catch (error) { invariant(nextEffect !== null, 'Should be working on an effect.'); captureCommitPhaseError(nextEffect, error); nextEffect = nextEffect.nextEffect; } } while (nextEffect !== null);
// We no longer need to track the active instance fiber focusedInstanceHandle = null;
if (enableProfilerTimer) { // Mark the current commit time to be shared by all Profilers in this // batch. This enables them to be grouped later. recordCommitTime(); }
// The next phase is the mutation phase, where we mutate the host tree. 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);
if (shouldFireAfterActiveInstanceBlur) { afterActiveInstanceBlur(); } 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 // 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; do { try { commitLayoutEffects(root, expirationTime); } 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; } 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. rootDoesHavePassiveEffects = false; rootWithPendingPassiveEffects = root; pendingPassiveEffectsExpirationTime = expirationTime; 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. nextEffect = firstEffect; while (nextEffect !== null) { const nextNextEffect = nextEffect.nextEffect; nextEffect.nextEffect = null; if (nextEffect.effectTag & Deletion) { detachFiberAfterEffects(nextEffect); } nextEffect = nextNextEffect; } }
// Check if there's remaining work on this root const remainingExpirationTime = root.firstPendingTime; if (remainingExpirationTime !== NoWork) { 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, remainingExpirationTime); } } else { // If there's no remaining work, we can clear the set of already failed // error boundaries. legacyErrorBoundariesThatAlreadyFailed = null; }
if (enableSchedulerTracing) { if (!rootDidHavePassiveEffects) { // If there are no passive effects, then we can complete the pending interactions. // Otherwise, we'll wait until after the passive effects are flushed. // Wait to do this until after remaining work has been scheduled, // so that we don't prematurely signal complete for interactions when there's e.g. hidden work. finishPendingInteractions(root, expirationTime); } }
if (remainingExpirationTime === Sync) { // 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 ((executionContext & LegacyUnbatchedContext) !== NoContext) { // This is a legacy edge case. We just committed the initial mount of // a ReactDOM.render-ed root inside of batchedUpdates. The commit fired // synchronously, but layout updates should be deferred until the end // of the batch. returnnull; }
// If layout work was scheduled, flush it now. flushSyncCallbackQueue();
exportconst PerformedWork = /* */0b00000000000001; functionmarkRootFinishedAtTime( root: FiberRoot, finishedExpirationTime: ExpirationTime, remainingExpirationTime: ExpirationTime, ): void{ // Update the range of pending times root.firstPendingTime = remainingExpirationTime; if (remainingExpirationTime < root.lastPendingTime) { // This usually means we've finished all the work, but it can also happen // when something gets downprioritized during render, like a hidden tree. root.lastPendingTime = remainingExpirationTime; }
// Update the range of suspended times. Treat everything higher priority or // equal to this update as unsuspended. if (finishedExpirationTime <= root.lastSuspendedTime) { // The entire suspended range is now unsuspended. root.firstSuspendedTime = root.lastSuspendedTime = root.nextKnownPendingLevel = NoWork; } elseif (finishedExpirationTime <= root.firstSuspendedTime) { // Part of the suspended range is now unsuspended. Narrow the range to // include everything between the unsuspended time (non-inclusive) and the // last suspended time. root.firstSuspendedTime = finishedExpirationTime - 1; }
if (finishedExpirationTime <= root.lastPingedTime) { // Clear the pinged time root.lastPingedTime = NoWork; }
if (finishedExpirationTime <= root.lastExpiredTime) { // Clear the expired time root.lastExpiredTime = NoWork; }
// Clear any pending updates that were just processed. clearPendingMutableSourceUpdates(root, finishedExpirationTime); }
const current = nextEffect.alternate; commitBeforeMutationEffectOnFiber(current, nextEffect);
resetCurrentDebugFiberInDEV(); } if ((effectTag & Passive) !== NoEffect) { // If there are passive effects, schedule a callback to flush at // the earliest opportunity. if (!rootDoesHavePassiveEffects) { rootDoesHavePassiveEffects = true; scheduleCallback(NormalPriority, () => { flushPassiveEffects(); returnnull; }); } } nextEffect = nextEffect.nextEffect; } }
functioncommitBeforeMutationLifeCycles( current: Fiber | null, finishedWork: Fiber, ): void{ switch (finishedWork.tag) { case FunctionComponent: case ForwardRef: case SimpleMemoComponent: case Block: { return; } case ClassComponent: { if (finishedWork.effectTag & Snapshot) { if (current !== null) { const prevProps = current.memoizedProps; const prevState = current.memoizedState; const instance = finishedWork.stateNode; const snapshot = instance.getSnapshotBeforeUpdate( finishedWork.elementType === finishedWork.type ? prevProps : resolveDefaultProps(finishedWork.type, prevProps), prevState, ); instance.__reactInternalSnapshotBeforeUpdate = snapshot; } } return; } case HostRoot: { if (supportsMutation) { if (finishedWork.effectTag & 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.', ); }
functioncommitMutationEffects(root: FiberRoot, renderPriorityLevel) { // TODO: Should probably move the bulk of this function to commitWork. while (nextEffect !== null) { setCurrentDebugFiberInDEV(nextEffect);
const effectTag = nextEffect.effectTag;
if (effectTag & ContentReset) { commitResetTextContent(nextEffect); }
if (effectTag & Ref) { const current = nextEffect.alternate; if (current !== null) { commitDetachRef(current); } }
// 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 primaryEffectTag = effectTag & (Placement | Update | Deletion | Hydrating); switch (primaryEffectTag) { 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.effectTag &= ~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.effectTag &= ~Placement;
// Update const current = nextEffect.alternate; commitWork(current, nextEffect); break; } case Hydrating: { nextEffect.effectTag &= ~Hydrating; break; } case HydratingAndUpdate: { nextEffect.effectTag &= ~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; } }
functionappendChildToContainer( container: Container, child: Instance | TextInstance, ): void{ let parentNode; if (container.nodeType === COMMENT_NODE) { parentNode = (container.parentNode: any); parentNode.insertBefore(child, container); } else { parentNode = container; parentNode.appendChild(child); } // This container might be used for a portal. // If something inside a portal is clicked, that click should bubble // through the React tree. However, on Mobile Safari the click would // never bubble through the *DOM* tree unless an ancestor with onclick // event exists. So we wouldn't see it and dispatch it. // This is why we ensure that non React root containers have inline onclick // defined. // https://github.com/facebook/react/issues/11918 const reactRootContainer = container._reactRootContainer; if ( (reactRootContainer === null || reactRootContainer === undefined) && parentNode.onclick === null ) { // TODO: This cast may not be sound for SVG, MathML or custom elements. trapClickOnNonInteractiveElement(((parentNode: any): HTMLElement)); } }