RenderRoot (); renderRoot(); renderRoot(); renderRoot(); renderRoot(); Case enters the commit phase:

  switch (workInProgressRootExitStatus) {

    / /...



    case RootErrored: {

      / /...



      // If we're already rendering synchronously, commit the root in its

      // errored state.

      / / the commit phase

      return commitRoot.bind(null, root);

    }

    case RootSuspended: {

      / /...



      // The work expired. Commit immediately.

      / / the commit phase

      return commitRoot.bind(null, root);

    }

    case RootSuspendedWithDelay: {

      / /...



      // The work expired. Commit immediately.

      / / the commit phase

      return commitRoot.bind(null, root);

    }

    case RootCompleted: {

      // The work completed. Ready to commit.

      / /...



      / / the commit phase

      return commitRoot.bind(null, root);

    }

    default: {

      invariant(false.'Unknown root exit status.');

    }

  }

Copy the code

CommitRoot ()/commitRootImpl() /commitRootImpl()

(1) Execute commitRootImpl() with the highest priority. (2) If there are dirty functions, use a callback to remove them

Source:

function commitRoot(root) {

  //ImmediatePriority, priority 99, highest priority, immediately executed

  / / the bind function, please see: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind



  // Get the scheduling priority and temporarily replace the current priority to execute the callback passed in

  runWithPriority(ImmediatePriority, commitRootImpl.bind(null, root));

  // If there are passive effects, schedule a callback to flush them. This goes

  // outside commitRootImpl so that it inherits the priority of the render.

  // If there are dirty effects, use a callback to clean them up

  // Because it is executed outside of commitRootImpl(), it inherits the priority of render

  if(rootWithPendingPassiveEffects ! = =null) {

    // Get the priority of render

    / / see: [the React of source code parsing scheduleWork (on)] (https://juejin.cn/post/6844903943017857031) in the "five, getCurrentPriorityLevel ()"

    const priorityLevel = getCurrentPriorityLevel();

    // Wrap the callback and update the status of the scheduling queue



    / / look at [the React of source code parsing scheduleWork (under)] (https://juejin.cn/post/6844903950789902349) of the [10, scheduleSyncCallback ()] of parsing

    scheduleCallback(priorityLevel, () => {

      // Clean up the dirt

      flushPassiveEffects();

      return null;

    });

  }

  return null;

}

Copy the code

(1) Execute runWithPriority() to get the scheduling priority, and temporarily replace the current priority to execute the callback passed in

Since the ImmediatePriority is the highest priority, the commitRootImpl() method is immediately executed

① runWithPriority() ¶

// Get the scheduling priority and temporarily replace the current priority to execute the callback passed in

export function runWithPriority<T> (

  reactPriorityLevel: ReactPriorityLevel,

  fn: (
) = >T.

) :T 
{

  // Get the scheduling priority

  const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);

  // Temporarily replace the current priority to execute the passed callback

  return Scheduler_runWithPriority(priorityLevel, fn);

}

Copy the code

(2) reactPriorityToSchedulerPriority () of the source code is as follows:

// Get the scheduling priority

function reactPriorityToSchedulerPriority(reactPriorityLevel{

  switch (reactPriorityLevel) {

    case ImmediatePriority:

      return Scheduler_ImmediatePriority;

    case UserBlockingPriority:

      return Scheduler_UserBlockingPriority;

    case NormalPriority:

      return Scheduler_NormalPriority;

    case LowPriority:

      return Scheduler_LowPriority;

    case IdlePriority:

      return Scheduler_IdlePriority;

    default:

      invariant(false.'Unknown priority level.');

  }

}

Copy the code

Unstable_runWithPriority () Scheduler_runWithPriority()

// Temporarily replace the current priority to execute the passed callback

function unstable_runWithPriority(priorityLevel, eventHandler{

  // The default is NormalPriority

  switch (priorityLevel) {

    case ImmediatePriority:

    case UserBlockingPriority:

    case NormalPriority:

    case LowPriority:

    case IdlePriority:

      break;

    default:

      priorityLevel = NormalPriority;

  }



  // Cache current priority currentPriorityLevel

  var previousPriorityLevel = currentPriorityLevel;

  // Temporarily replace the priority to execute eventHandler()

  currentPriorityLevel = priorityLevel;

  // Finally is executed when a try is returned

  try {

    return eventHandler();

  } finally {

    // Restore the current priority to the previous priority

    currentPriorityLevel = previousPriorityLevel;

  }

}

Copy the code

(2) After runWithPriority(), if there are still dirty effects, use a callback to clean them up

React: getCurrentPriorityLevel(); getCurrentPriorityLevel(); getCurrentPriorityLevel();

ScheduleCallback () scheduleSyncCallback() scheduleSyncCallback() scheduleSyncCallback()

The callback function is flushPassiveEffects(), which flushPassiveEffects(), and flushPassiveEffects().

(3) The core function of commitRoot() is commitRootImpl().

CommitRootImpl () (1) Determine whether to commit according to the effect chain. ① When performing the commit, perform three sub-stages of “before mutation”, “mutation” and “layout”; otherwise, skip the commit stage quickly and go through the report process

(2) Determine whether the commit will generate a dirty update, and if so, process it

(3) Check whether the target fiber has any remaining work to do. (1) If there is any remaining work, execute these scheduling tasks. (2) If there is no error, clear the “error boundary”.

(4) Refresh the synchronization queue

Source:

function commitRootImpl(root) {

  // Clean up the dirt

  flushPassiveEffects();

  //dev code can not be read

  //flushRenderPhaseStrictModeWarningsInDEV();

  //flushSuspensePriorityWarningInDEV();



  / / = = = = = = = the context judgment

  invariant(

    (executionContext & (RenderContext | CommitContext)) === NoContext,

    'Should not already be working.'.

  );



  // Complete the task

  const finishedWork = root.finishedWork;

  // The priority of the dispatch

  const expirationTime = root.finishedExpirationTime;

  // Indicates that this node has no task to update, and returns directly

  if (finishedWork === null) {

    return null;

  }

  // Assign to finishedWork and expirationTime and reset to the original value

  // Commit finishedWork to expirationTime

  root.finishedWork = null;

  root.finishedExpirationTime = NoWork;



  / / the error judgment

  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.

  //commitRoot is the last stage and will not be called asynchronously, so callback attributes will be cleared

  root.callbackNode = null;

  root.callbackExpirationTime = NoWork;



  // The timer can be skipped

  startCommitTimer();



  // Update the first and last pending times on this root. The new first

  // pending time is whatever is left on the root fiber.

  // Update priority of the target node

  const updateExpirationTimeBeforeCommit = finishedWork.expirationTime;

  // Update priority of the child node, that is, the highest priority task of all the child nodes

  / / about childExpirationTime, see: https://juejin.cn/post/6844903997506060301

  const childExpirationTimeBeforeCommit = finishedWork.childExpirationTime;

  // Get the highest expirationTime

  const firstPendingTimeBeforeCommit =

    childExpirationTimeBeforeCommit > updateExpirationTimeBeforeCommit

      ? childExpirationTimeBeforeCommit

      : updateExpirationTimeBeforeCommit;

  //firstPendingTime is the expirationTime of the highest priority task

  root.firstPendingTime = firstPendingTimeBeforeCommit;

  // If firstPendingTime

  if (firstPendingTimeBeforeCommit < 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 = firstPendingTimeBeforeCommit;

  }

  // If the target node root is the workInProgressRoot node being updated

  // Set the associated value to its initial value, since it will be updated later

  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.

  // Get the effect chain

  let firstEffect;

  // If the effectTag of RootFiber has a value, that is, RootFiber is also committed

  // Insert its finishedWork into the effect chain and place it on lasteffect.nexteffect at the end of the chain

  if (finishedWork.effectTag > PerformedWork) {

    // 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;

  }



  // The first fiber object on the effect chain that needs to be updated

  if(firstEffect ! = =null) {

    //=======context, skip ========= for now

    // const prevExecutionContext = executionContext;

    // executionContext |= CommitContext;

    // let prevInteractions: Set<Interaction> | null = null;

    // if (enableSchedulerTracing) {

    // prevInteractions = __interactionsRef.current;

    // __interactionsRef.current = root.memoizedInteractions;

    // }



    // 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 commit phase is divided into several sub-phases. We walked through the list of effects for each stage individually: all mutation effects precede all layout effects



    // 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.

    // The first subphase is the "before mutation" phase, in which React reads the state of the fiber tree,

    // This is also the reason for the getSnapshotBeforeUpdate name



    // The "before mutation" subphase has been started

    startCommitSnapshotEffectsTimer();

    / / update the currently selected DOM node, generally for the document. The activeElement | | document. The body

    prepareForCommit(root.containerInfo);

    nextEffect = firstEffect;

    //=========== first while loop ==============

    do {

      if (__DEV__) {

        // Delete dev code

      } else {

        try {

          // Call the lifecycle method getSnapshotBeforeUpdate on classComponent

          / / about getSnapshotBeforeUpdate, see: https://zh-hans.reactjs.org/docs/react-component.html#getsnapshotbeforeupdate

          commitBeforeMutationEffects();

        } catch (error) {

invariant(nextEffect ! = =null.'Should be working on an effect.');

          captureCommitPhaseError(nextEffect, error);

          nextEffect = nextEffect.nextEffect;

        }

      }

    } while(nextEffect ! = =null);

    // Flag the "before mutation" subphase has ended

    stopCommitSnapshotEffectsTimer();



    //======profiler related, skip ====== for now

    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.

    // the mutation subphase is started

    startCommitHostEffectsTimer();

    nextEffect = firstEffect;

    //============= second while loop =================

    do {

      if (__DEV__) {

        // Delete dev code

      } else {

        try {

          // Submit side effects to HostComponent, i.e., DOM node operations (add, delete, modify)

          commitMutationEffects();

        } catch (error) {

invariant(nextEffect ! = =null.'Should be working on an effect.');

          captureCommitPhaseError(nextEffect, error);

          nextEffect = nextEffect.nextEffect;

        }

      }

    } while(nextEffect ! = =null);

    // Mark the "mutation" subphase has ended

    stopCommitHostEffectsTimer();

    // The focus of the selected DOM may be lost when performing DOM operations, such as deletion

    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.



    // After mutation, the ongoing work-in-progress tree became the current tree

    // To ensure that the previous Fiber tree is the current tree during componentWillUnmount

    // To ensure that the finishedWork of work-in-progress is current before the "Layout" sub-stage



    Update root.current (fiber tree) to commit (fiber tree) as each subphase progresses

    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.

    // The tag starts the "layout" sub-stage

    // This phase triggers the lifecycles commit of all components

    startCommitLifeCyclesTimer();

    nextEffect = firstEffect;

    / / = = = = = = = = = = = = = the third while loop = = = = = = = = = = = = = = = = = = = = = = = = = =

    do {

      if (__DEV__) {

        // Delete dev code

      } else {

        try {

          //commit lifecycles, the API that triggers the lifecycle

          commitLayoutEffects(root, expirationTime);

        } catch (error) {

invariant(nextEffect ! = =null.'Should be working on an effect.');

          captureCommitPhaseError(nextEffect, error);

          nextEffect = nextEffect.nextEffect;

        }

      }

    } while(nextEffect ! = =null);

    // Mark the "Layout" sub-stage has ended

    stopCommitLifeCyclesTimer();

    // Effect being committed is set to null, indicating that the commit is complete

    nextEffect = null;



    // Tell Scheduler to yield at the end of the frame, so the browser has an

    // opportunity to paint.

    //React uses up resources, telling the browser to draw the UI

    requestPaint();



    //======= skip ============= for now

    if (enableSchedulerTracing) {

      __interactionsRef.current = ((prevInteractions: any): Set<Interaction>);

    }

    executionContext = prevExecutionContext;

  }

  // If the effect chain has no fiber object to update

  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.



    // Quickly pass the COMMIT phase and go through the report process

    startCommitSnapshotEffectsTimer();

    stopCommitSnapshotEffectsTimer();

    if (enableProfilerTimer) {

      recordCommitTime();

    }

    startCommitHostEffectsTimer();

    stopCommitHostEffectsTimer();

    startCommitLifeCyclesTimer();

    stopCommitLifeCyclesTimer();

  }

  // Mark the end of the COMMIT phase

  stopCommitTimer();

  // Determine whether this commit will generate new updates, i.e. dirty effects

  const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;

  // If there is dirty processing

  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;

  }



  // Check if there's remaining work on this root

  // Check if there is any work left

  const remainingExpirationTime = root.firstPendingTime;

  // If there is any work left

  if(remainingExpirationTime ! == NoWork) {

    // Calculate the current time

    const currentTime = requestCurrentTime();

    // Deduce the priority through expirationTime

    const priorityLevel = inferPriorityFromExpirationTime(

      currentTime,

      remainingExpirationTime,

    );



    if (enableSchedulerTracing) {

      // Work derived from render phase, may refer to new update or new error

      if(spawnedWorkDuringRender ! = =null) {

        const expirationTimes = spawnedWorkDuringRender;

        spawnedWorkDuringRender = null;

        // Loop through the scheduleInteractions

        for (let i = 0; i < expirationTimes.length; i++) {

          // Interaction with schedule

          / / see: [the React of source code parsing scheduleWork (on)] (https://juejin.cn/post/6844903943017857031) in the "six, schedulePendingInteractions ()"

          scheduleInteractions(

            root,

            expirationTimes[i],

            root.memoizedInteractions,

          );

        }

      }

    }

    // Call callback synchronously

    // Access callback and expirationTime on root.

    // When a new callback is called, compare to update expirationTime



    / / see: [the React of source code parsing scheduleWork (under)] (https://juejin.cn/post/6844903950789902349) in the "eight, scheduleCallbackForRoot ()"

    scheduleCallbackForRoot(root, priorityLevel, remainingExpirationTime);

  }

  // If there is no work left, the commit is successful, and the "error bound" list is cleared

  else {

    // If there's no remaining work, we can clear the set of already failed

    // error boundaries.

    legacyErrorBoundariesThatAlreadyFailed = null;

  }



  if (enableSchedulerTracing) {

    // React can clean up completed interactions after the dirty effects of the commit are removed

    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.



      // Clear already completed interactions, if suspended, and save the interactions for subsequent renderings

      finishPendingInteractions(root, expirationTime);

    }

  }

  //devTools

  onCommitRoot(finishedWork.stateNode, expirationTime);



  // The rest of the work is synchronized tasks

  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.



    // Count the number of times a synchronous re-render is performed to determine if it is an infinite loop

    if (root === rootWithNestedUpdates) {

      nestedUpdateCount++;

    } else {

      nestedUpdateCount = 0;

      rootWithNestedUpdates = root;

    }

  } else {

    nestedUpdateCount = 0;

  }

  Throw error if an error is caught

  if (hasUncaughtError) {

    hasUncaughtError = false;

    const error = firstUncaughtError;

    firstUncaughtError = null;

    throw error;

  }



  / / to watch

  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.

    return null;

  }



  // If layout work was scheduled, flush it now.

  // If the task in the "Layout" phase is already scheduled, clear it immediately



  // Refresh the synchronization task queue

  / / see: [the React of source code parsing scheduleWork (under)] (https://juejin.cn/post/6844903950789902349) in the "12, flushSyncCallbackQueue ()"

  flushSyncCallbackQueue();

  return null;

}

Copy the code

It is a long time, but the core part is the three sub-stages. The while loop. The whole can be viewed as three parts: (1) preparation part (2) Commit part (three sub-stages) (3) closing part


Here’s what commitRootImpl() does, in source order (some of which I’ll comment directly) :

(1) Execute flushPassiveEffects() to remove the dirty effect

(2) According to the update priority of the target node and childExpirationTime of the child nodes, compare and obtain the highest expirationTime, so as to judge whether all the render work has been completed.

React’s childExpirationTime

(3) Determine whether the target fiber itself also needs to commit. If so, perform linked list operation and put its finishedWork on lasteffect.nexteffect, the end of the effect chain

(4) If firstEffect is not null, it indicates that the task has been submitted, then three substages are performed.

Perform commitBeforeMutationEffects (), nature is to call classComponent — getSnapshotBeforeUpdate lifecycle methods on the ()

On the getSnapshotBeforeUpdate introduction and function, please see: zh-hans.reactjs.org/docs/react-…

(2) The second substage “mutation” performs commitMutationEffects(), which is to submit side effects of HostComponent, namely DOM node operations (adding, deleting and modifying).

React HostComponent update HostComponent update HostComponent

React HostComponent update

(3) The third sub-stage “layout” implements commitLayoutEffects(), which triggers the API of the component life cycle

If firstEffect is null, there are no fiber objects in the effect chain that need to be updated

So you see three sets of startCommitXXXTimer() and endCommitXXXTimer()

(6) By now, the commit is basically finished, but the COMMIT stage may also generate a new work, namely remainingExpirationTime

When there are remaining works, loop through them, execute scheduleInteractions() in turn, schedule them to scheduled tasks, and execute them through scheduleCallbackForRoot()

1) about scheduleInteractions (), please see: the React of source code parsing scheduleWork (on) in the “six, schedulePendingInteractions ()”

ScheduleCallbackForRoot () scheduleCallbackForRoot()

(7) Finally, run flushSyncCallbackQueue() to flush the synchronization task queue

FlushSyncCallbackQueue (); flushSyncCallbackQueue();

(8) Finally, I wrote comments on each line of the source code to familiarize you with the overall commitRoot process.

Making commitRoot ()/commitRootImpl () : github.com/AttackXiaoJ…



(after)