Click on the React source debug repository.

When the Render phase is complete, it means that all updates to the in-memory workInProgress tree have been completed, including fiber node updates, diff, effectTag tags, and effectList collection. The complete form of the workInProgress tree is as follows:

While they differ structurally from the current tree, the changing Fiber nodes also exist in the workInProgress tree, but applying these nodes to the DOM does not loop through the entire tree. Instead, it loops through the linked list of effectLists, ensuring that work is done only on the changing nodes.

So looping through the effectList to apply the updated Fiber nodes to the page is the main work of the COMMIT phase.

The entry point to the COMMIT phase is the commitRoot function, which tells the Scheduler to schedule work for the Commit phase with an immediate priority.

function commitRoot(root) {
  const renderPriorityLevel = getCurrentPriorityLevel();
  runWithPriority(
    ImmediateSchedulerPriority,
    commitRootImpl.bind(null, root, renderPriorityLevel),
  );
  return null;
}

Copy the code

Scheduler schedules commitRootImpl, which is the core implementation of the COMMIT phase, which is divided into three parts.

Commit Process overview

The COMMIT phase focuses on processing the effectList collected on root. Before the real work begins, there is a preparatory phase that involves assigning variables and adding root’s effects to the effectList. Then we started working on effectList in three stages:

  • Before mutation: Reads the state of the component before it changes. For class components, call getSnapshotBeforeUpdate to get information about the component instance before DOM changes. For function components, asynchronous scheduling useEffect.
  • Mutation: DOM manipulation was performed on the HostComponent. For class components, call componentWillUnmount; For function components, useLayoutEffect’s destruction function is executed.
  • Layout: after the DOM operation is completed, read the state of the component, for the class component, call the life cycle componentDidMount and componentDidUpdate, call the setState callback; Fills the Effect execution array with useEffect against the function component and schedules useEffect

UseEffect scheduling for function components by before mutation and layout is mutually exclusive and can only be initiated once

The time when the workInProgress tree switches to the current tree is after mutation and before layout starts. The reason for doing this is that when calling componentWillUnmount of the class component in the mutation stage, the component information before uninstallation can also be obtained. ComponentDidMount /Update gets updated component information when called during the Layout phase.

function commitRootImpl(root, renderPriorityLevel) {

  UseEffect is executed once before the commit phase
  do {
    flushPassiveEffects();
  } while(rootWithPendingPassiveEffects ! = =null);

  / / preparation stage -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

  const finishedWork = root.finishedWork;
  const lanes = root.finishedLanes;
  if (finishedWork === null) {
    return null;
  }
  root.finishedWork = null;
  root.finishedLanes = NoLanes;

  root.callbackNode = null;
  root.callbackId = NoLanes;

  // effectList to connect root to the end of effectList
  let firstEffect;
  if (finishedWork.effectTag > PerformedWork) {
    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;
  }

  // Start processing effectList
  if(firstEffect ! = =null) {.../ / before mutation stages -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    nextEffect = firstEffect;
    do{... }while(nextEffect ! = =null); ./ / mutation stages -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    nextEffect = firstEffect;
    do{... }while(nextEffect ! = =null);

    // Switch the wprkInProgress tree to the current tree
    root.current = finishedWork;

    / / layout stage -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
    nextEffect = firstEffect;
    do{... }while(nextEffect ! = =null);

    nextEffect = null;

    // Tell the browser to draw
    requestPaint();

  } else {
    // Switch the wprkInProgress tree to the current tree without effectList
    root.current = finishedWork;

  }

  const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;

  // Get the unprocessed priority, such as the priority of the previously skipped task
  remainingLanes = root.pendingLanes;
  // Place the skipped priorities on pendingLanes (priorities to be processed) on root
  markRootFinished(root, remainingLanes);

  /* * After each commit stage is complete, one more run is implemented to ensure that any additional tasks need to be ensureRootIsScheduled. * For example, after a high-priority queue-jumping update is completed and after a commit is completed, it is executed again to ensure that the previously skipped low-priority tasks are rescheduled * * */ensureRootIsScheduled(root, now()); .return null;
}
Copy the code

The following sections give a detailed explanation of each of the three stages.

before Mutation

The entrance of the function is commitBeforeMutationEffects beforeMutation stage

nextEffect = firstEffect;
do {
  try {
    commitBeforeMutationEffects();
  } catch(error) { ... }}while(nextEffect ! = =null);
Copy the code

Its main function is to call getSnapshotBeforeUpdate of the class component and asynchronously schedule useEffect for the function component.

function commitBeforeMutationEffects() {
  while(nextEffect ! = =null) {
    constcurrent = nextEffect.alternate; .const flags = nextEffect.flags;
    if((flags & Snapshot) ! == NoFlags) {/ / getSnapshotBeforeUpdate through commitBeforeMutationEffectOnFiber calls
      commitBeforeMutationEffectOnFiber(current, nextEffect);
    }

    if((flags & Passive) ! == NoFlags) {useEffect
      if(! rootDoesHavePassiveEffects) { rootDoesHavePassiveEffects =true;
        scheduleCallback(NormalSchedulerPriority, () = > {
          flushPassiveEffects();
          return null; }); } } nextEffect = nextEffect.nextEffect; }}Copy the code

CommitBeforeMutationEffectOnFiber code as follows

function commitBeforeMutationLifeCycles(
  current: Fiber | null,
  finishedWork: Fiber,
) :void {
  switch (finishedWork.tag) {
    ...
    case ClassComponent: {
      if (finishedWork.flags & Snapshot) {
        if(current ! = =null) {
          const prevProps = current.memoizedProps;
          const prevState = current.memoizedState;
          const instance = finishedWork.stateNode;
          / / call getSnapshotBeforeUpdate
          const snapshot = instance.getSnapshotBeforeUpdate(
            finishedWork.elementType === finishedWork.type
              ? prevProps
              : resolveDefaultProps(finishedWork.type, prevProps),
            prevState,
          );
          // Stores the return value on an internal property to facilitate componentDidUpdate retrievalinstance.__reactInternalSnapshotBeforeUpdate = snapshot; }}return; }... }}Copy the code

mutation

The mutation stage will actually operate the DOM node, involving operations such as add, delete and change. The entry function is commitMutationEffects

    nextEffect = firstEffect;
    do {
      try {
        commitMutationEffects(root, renderPriorityLevel);
      } catch(error) { ... nextEffect = nextEffect.nextEffect; }}while(nextEffect ! = =null);
Copy the code

Because of the complexity of the process, I’ve written three articles to explain these three DOM manipulations, if you want to learn more about them. This article was written before the official release of 17, so the source version is taken from 17.0.0-alpha0.

React and DOM stuff – node new algorithms

React and DOM stuff – node deletion algorithms

React and DOM stuff – node updates

Layout stage

The entry function for the Layout stage is commitLayoutEffects.

nextEffect = firstEffect;
do {
  try {
    commitLayoutEffects(root, lanes);
  } catch(error) { ... nextEffect = nextEffect.nextEffect; }}while(nextEffect ! = =null);
Copy the code

We’ll just focus on classComponent and functionComponent. For the former, call the life cycle componentDidMount and componentDidUpdate, call the setState callback; For the latter, the effect execution array is filled with useEffect, and the useEffect is scheduled (see my article on useEffect and useLayoutEffect for details).

function commitLifeCycles(
  finishedRoot: FiberRoot,
  current: Fiber | null,
  finishedWork: Fiber,
  committedLanes: Lanes,
) :void {
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent:
    case Block: {
      // Create the useLayoutEffect
      commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);

      // Populate the effect implementation array for useEffect
      schedulePassiveEffects(finishedWork);
      return;
    }
    case ClassComponent: {
      const instance = finishedWork.stateNode;
      if (finishedWork.flags & Update) {
        if (current === null) {
          // For initial mount phase, call componentDidMount
          instance.componentDidMount();
        } else {
          // For update phase, call componentDidUpdate
          const prevProps =
            finishedWork.elementType === finishedWork.type
              ? current.memoizedProps
              : resolveDefaultProps(finishedWork.type, current.memoizedProps);
          const prevState = current.memoizedState;

          instance.componentDidUpdate(
            prevProps,
            prevState,
            // Pass in the result of getSnapshotBeforeUpdateinstance.__reactInternalSnapshotBeforeUpdate, ); }}// Call the setState callback
      const updateQueue: UpdateQueue<
        *,
      > | null = (finishedWork.updateQueue: any);
      if(updateQueue ! = =null) {

        commitUpdateQueue(finishedWork, updateQueue, instance);
      }
      return; }... }}Copy the code

conclusion

The COMMIT phase divides effectList processing into three phases to ensure that functions with different life cycles are called in a timely manner. In contrast to useEffectLayout, which executes synchronously, the asynchronous dispatch of useEffect provides a side effect operator that does not block page rendering. Additionally, the markup of unprocessed priorities on root and the invocation of “ensureRootIsScheduled” enables skipped low-priority tasks to be scheduled again. When the COMMIT phase is complete, the update is over.