Interpretation of React's source code and principles (7): commit stage

Written at the beginning of the column (stack armor)

  1. The author is not an expert in front-end technology, but just a novice in front-end technology who likes to learn new things. He wants to learn source code just to cope with the rapidly declining front-end market and the need to find a job. This column is the author's own thinking in the process of learning And experience, there are also many parts that refer to other tutorials. If there are errors or problems, please point them out to the author. The author guarantees that the content is 100% correct. Please do not use this column as a reference answer.

  2. The reading of this column requires you to have a certain foundation of React, JavaScript and front-end engineering. The author will not explain many basic knowledge points, such as: what is babel, what is the syntax of jsx, please refer to it when necessary material.

  3. Many parts of this column refer to a large number of other tutorials. If there are similarities, the author plagiarized them. Therefore, this tutorial is completely open source. You can integrate, summarize and add your own understanding to various tutorials as the author.

Contents of this section

In this section, we will talk about the operation of React in commit , because in the previous tutorial, we have already talked about how React turns the element node into a Fiber tree, and how to update our Fiber tree through the DIFF algorithm, so now we need Synchronize the update of our Fiber tree to our real DOM and show it to the user. This stage is the Commit stage

from the last stage

Now let's turn our attention back to our fourth tutorial - updateContainer the relevant part, we can see the two functions we mentioned before: performConcurrentWorkOnRoot, performSyncWorkOnRoot, let's take a look at their structure:

  • Their logic is roughly the same, except that they ConcurrentWorkneed to deal with the process scheduling and priority issues related to concurrent tasks
  • Through the study of tutorials 5 and 6, we know that in renderRootSyncand renderRootConcurrent, we generated our Fiber tree, so after these two functions, we can get our Fiber tree
  • After going through a series of error handling functions (probably something wrong with generating our Fiber) we finally arrive at commitRootthis function, which passes in the structure of the Fiber tree we just generated. Among them, the sync task is called directly, and the Concurrent task needs to pass finishConcurrentRenderthis function to determine whether our rendering is completed according to the state, and then call commitRootWhenReady(which calls commitRoot) to enter our commitstage (about the difference between these two tasks, we will Separately later)
export function performSyncWorkOnRoot(root: FiberRoot): null {
    
    
  // 省略....

  flushPassiveEffects();

  // 优先级相关
  let lanes = getNextLanes(root, NoLanes);
  if (!includesSyncLane(lanes)) {
    
    
    ensureRootIsScheduled(root);
    return null;
  }
  // 生成 Fiber 的函数
  let exitStatus = renderRootSync(root, lanes);//下面都是处理生成失败的处理
  if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
    
    
    //...
  }
  if (exitStatus === RootFatalErrored) {
    
    
    //...
  }
  if (exitStatus === RootDidNotComplete) {
    
    
    //...
  }

  // 生成完毕,返回我们的 WorkInProgress 树的根节点
  const finishedWork: Fiber = (root.current.alternate: any);
  root.finishedWork = finishedWork;
  root.finishedLanes = lanes;
  // 进入 commit 阶段
  commitRoot(
    root,
    workInProgressRootRecoverableErrors,
    workInProgressTransitions,
  );
  ensureRootIsScheduled(root);
  return null;
}

export function performConcurrentWorkOnRoot(
  root: FiberRoot,
  didTimeout: boolean,
): RenderTaskFn | null {
    
    
  // 省略处理优先级相关、调度相关的 ....
  const shouldTimeSlice =
    !includesBlockingLane(root, lanes) &&
    !includesExpiredLane(root, lanes) &&
    (disableSchedulerTimeoutInWorkLoop || !didTimeout);
  // 生成 Fiber 的函数
  let exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes)
    : renderRootSync(root, lanes);
  	//下面都是处理生成失败的处理....省略
    
    // 生成完毕,返回我们的 WorkInProgress 树的根节点
    root.finishedWork = finishedWork;
    root.finishedLanes = lanes;
    // 进入 commit 阶段
    finishConcurrentRender(root, exitStatus, finishedWork, lanes);
  }
  ensureRootIsScheduled(root);
  return getContinuationForRoot(root, originalCallbackNode);
}

// finishConcurrentRender 判定是不是可以进入 commit 阶段,具体的分析之后我们会单独开一篇
function finishConcurrentRender(
  root: FiberRoot,
  exitStatus: RootExitStatus,
  finishedWork: Fiber,
  lanes: Lanes,
) {
    
    
  // 根据状态判定,
  switch (exitStatus) {
    
    
    case RootInProgress:
    case RootFatalErrored: {
    
    
      throw new Error('Root did not complete. This is a bug in React.');
    }
    case RootErrored: {
    
    
      commitRootWhenReady(
        root,
        finishedWork,
        workInProgressRootRecoverableErrors,
        workInProgressTransitions,
        lanes,
      );
      break;
    }
    case RootSuspended: {
    
    
      // 省略....
      commitRootWhenReady(
        root,
        finishedWork,
        workInProgressRootRecoverableErrors,
        workInProgressTransitions,
        lanes,
      );
      break;
    }
    case RootSuspendedWithDelay: {
    
    
      // 省略....
      commitRootWhenReady(
        root,
        finishedWork,
        workInProgressRootRecoverableErrors,
        workInProgressTransitions,
        lanes,
      );
      break;
    }
    case RootCompleted: {
    
    
      commitRootWhenReady(
        root,
        finishedWork,
        workInProgressRootRecoverableErrors,
        workInProgressTransitions,
        lanes,
      );
      break;
    }
    default: {
    
    
      throw new Error('Unknown root exit status.');
    }
  }
}

commitRoot

So it can be seen that commitRootthis function is the unified entry of the function after we generate our Fiber, and it calls commitRootImplthis function again. Let's see what this function does now. First of all, it needs to be clear that a one-way linked list effectList of Fiber nodes that need to perform side effects is saved on rootFiber.firstEffect, and the changed props are saved in the updateQueue of these Fiber nodes:

  • It first executes the useEffect callback and other synchronous tasks as they may trigger a new render
  • Then assign values ​​to our variables and reset the FiberRoot state
  • After that, it traverses the effectList three times, processing the three stages of BeforeMutationEffects, MutationEffects and LayoutEffects respectively:
    • BeforeMutationEffectsMainly update the state, props, etc. on the class component instance, and execute the getSnapshotBeforeUpdate life cycle function
    • MutationEffectsIt mainly completes the execution of side effects, mainly including resetting text nodes and inserting, deleting and updating real dom nodes.
    • LayoutEffectsMainly to trigger componentDidMount, componentDidUpdate and various callback functions, etc.
  • Finally, it also needs to deal with useEffect-related content, because some life cycle hooks will be triggered during the commit phase, so related synchronization tasks must also be performed
function commitRoot(
  root: FiberRoot,
  recoverableErrors: null | Array<CapturedValue<mixed>>,
  transitions: Array<Transition> | null,
) {
    
    
  const previousUpdateLanePriority = getCurrentUpdatePriority();
  const prevTransition = ReactCurrentBatchConfig.transition;
  try {
    
    
    ReactCurrentBatchConfig.transition = null;
    setCurrentUpdatePriority(DiscreteEventPriority);
    commitRootImpl(
      root,
      recoverableErrors,
      transitions,
      previousUpdateLanePriority,
    );
  } finally {
    
    
    ReactCurrentBatchConfig.transition = prevTransition;
    setCurrentUpdatePriority(previousUpdateLanePriority);
  }

  return null;
}

function commitRootImpl(
  root: FiberRoot,
  recoverableErrors: null | Array<mixed>,
  transitions: Array<Transition> | null,
  renderPriorityLevel: EventPriority,
) {
    
    
  // 调用flushPassiveEffects执行完所有effect的任务
  do {
    
    
    flushPassiveEffects();
  } while (rootWithPendingPassiveEffects !== null);
	
  // 优先级调度  
  const finishedWork = root.finishedWork;
  const lanes = root.finishedLanes;
  if (enableSchedulingProfiler) {
    
    
    markCommitStarted(lanes);
  }

  if (finishedWork === null) {
    
    
  	// 异常处理
  }
      
  //重置 FiberRoot 
  root.finishedWork = null;
  root.finishedLanes = NoLanes;
  root.callbackNode = null;
  root.callbackPriority = NoLane;
      
  //省略优先级相关的....
  // 重置全局变量    
  if (root === workInProgressRoot) {
    
    
    workInProgressRoot = null;
    workInProgress = null;
    workInProgressRootRenderLanes = NoLanes;
  } else {
    
    
  }

  // 处理 useEffect相关内容
  if (
    (finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
    (finishedWork.flags & PassiveMask) !== NoFlags
  ) {
    
    
    if (!rootDoesHavePassiveEffects) {
    
    
      rootDoesHavePassiveEffects = true;
      pendingPassiveEffectsRemainingLanes = remainingLanes;
      pendingPassiveTransitions = transitions;
      scheduleCallback(NormalSchedulerPriority, () => {
    
    
        flushPassiveEffects();
        return null;
      });
    }
  }

  // 第一次遍历,执行 commitBeforeMutationEffects
  const subtreeHasEffects =
    (finishedWork.subtreeFlags &
      (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
    NoFlags;
  const rootHasEffect =
    (finishedWork.flags &
      (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
    NoFlags;

  if (subtreeHasEffects || rootHasEffect) {
    
    
    const prevTransition = ReactCurrentBatchConfig.transition;
    ReactCurrentBatchConfig.transition = null;
    const previousPriority = getCurrentUpdatePriority();
    setCurrentUpdatePriority(DiscreteEventPriority);

    const prevExecutionContext = executionContext;
    executionContext |= CommitContext;
    ReactCurrentOwner.current = null;
    const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
      root,
      finishedWork,
    );

    if (enableProfilerTimer) {
    
    
      recordCommitTime();
    }

    if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
    
    
      rootCommittingMutationOrLayoutEffects = root;
    }

    // 第二次遍历,执行 commitMutationEffects
    commitMutationEffects(root, finishedWork, lanes);
    if (enableCreateEventHandleAPI) {
    
    
      if (shouldFireAfterActiveInstanceBlur) {
    
    
        afterActiveInstanceBlur();
      }
    }
    resetAfterCommit(root.containerInfo);

    root.current = finishedWork;

    if (enableSchedulingProfiler) {
    
    
      markLayoutEffectsStarted(lanes);
    }
    // 第三次遍历,执行 commitLayoutEffects
    commitLayoutEffects(finishedWork, root, lanes);

    if (enableSchedulingProfiler) {
    
    
      markLayoutEffectsStopped();
    }

    if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
    
    
      rootCommittingMutationOrLayoutEffects = null;
    }

    requestPaint();

    executionContext = prevExecutionContext;
    setCurrentUpdatePriority(previousPriority);
    ReactCurrentBatchConfig.transition = prevTransition;
  } else {
    
    
    // 没有任何副作用
    root.current = finishedWork;
    if (enableProfilerTimer) {
    
    
      recordCommitTime();
    }
  }
  const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
  // 处理 useEffect相关内容
  if (rootDoesHavePassiveEffects) {
    
    
    rootDoesHavePassiveEffects = false;
    rootWithPendingPassiveEffects = root;
    pendingPassiveEffectsLanes = lanes;
  } else {
    
    
    releaseRootPooledCache(root, remainingLanes);
  }
  // 省略优先级相关和DEV模式相关....
  // 触发一次新的调度,确保任何附加的任务被调度
  ensureRootIsScheduled(root, now());
  // 处理componentDidMount等生命周期或者useLayoutEffect等同步任务
  flushSyncCallbacks();
  return null;
}

BeforeMutationEffects

Let's look at each stage in turn, and at BeforeMutationEffects this stage, let's see what it does:

  • This stage will call commitBeforeMutationEffectsthe function processing

  • commitBeforeMutationEffectsThe function will be called in turn commitBeforeMutationEffects_begin, commitBeforeMutationEffects_complete, commitBeforeMutationEffectsOnFiber, getSnapShotBeforeUpdatefunction, which triggers getSnapShotBeforeUpdatethis life cycle

  • commitBeforeMutationEffects_beginIn the function, the children that need to be deleted in the current stage (generated in the diff of the previous stage) are obtained, and then related operations are performed on them, mainly processing focusand blur. Then traverse deeply until there are no children

  • commitBeforeMutationEffects_completeWhen the node we traverse has no children, we traverse its brothers, that is to say, commitBeforeMutationEffects_beginthe function and commitBeforeMutationEffects_completethe function traverse our Fiber together (the traversal method is roughly the same as the previous traversal element)

  • commitBeforeMutationEffectsOnFiberIt is used to process each Fiber, mainly to process the ClassComponent component, update the state, props, etc. on the instance, and execute the getSnapshotBeforeUpdate lifecycle function:

export function commitBeforeMutationEffects(
  root: FiberRoot,
  firstChild: Fiber,
) {
    
    
  // 准备提交
  focusedInstanceHandle = prepareForCommit(root.containerInfo);
  nextEffect = firstChild;
  // 开始提交
  commitBeforeMutationEffects_begin();
  const shouldFire = shouldFireAfterActiveInstanceBlur;
  shouldFireAfterActiveInstanceBlur = false;
  focusedInstanceHandle = null;
  return shouldFire;
}

function commitBeforeMutationEffects_begin() {
    
    
  while (nextEffect !== null) {
    
    
    const fiber = nextEffect;
    if (enableCreateEventHandleAPI) {
    
    
	  // 获取需要删除的内容
      const deletions = fiber.deletions;
      if (deletions !== null) {
    
    
        for (let i = 0; i < deletions.length; i++) {
    
    
          const deletion = deletions[i];
          commitBeforeMutationEffectsDeletion(deletion);
        }
      }
    }
    const child = fiber.child;
    if (
      (fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
      child !== null
    ) {
    
    
      child.return = fiber;
      nextEffect = child;
    } else {
    
    
      // 提交结束
      commitBeforeMutationEffects_complete();
    }
  }
}
// 提交完毕
function commitBeforeMutationEffects_complete() {
    
    
  while (nextEffect !== null) {
    
    
    const fiber = nextEffect;
    setCurrentDebugFiberInDEV(fiber);
    try {
    
    
      commitBeforeMutationEffectsOnFiber(fiber);
    } catch (error) {
    
    
      captureCommitPhaseError(fiber, fiber.return, error);
    }
    resetCurrentDebugFiberInDEV();

    const sibling = fiber.sibling;
    if (sibling !== null) {
    
    
      sibling.return = fiber.return;
      nextEffect = sibling;
      return;
    }
    nextEffect = fiber.return;
  }
}

function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
    
    
  const current = finishedWork.alternate;
  const flags = finishedWork.flags;

  //...省略

  if ((flags & Snapshot) !== NoFlags) {
    
    
    setCurrentDebugFiberInDEV(finishedWork);
    switch (finishedWork.tag) {
    
    
      case FunctionComponent:
      case ForwardRef:
      case SimpleMemoComponent: {
    
    
        break;
      }
      case ClassComponent: {
    
    
        if (current !== null) {
    
    
          const prevProps = current.memoizedProps;
          const prevState = current.memoizedState;
          const instance = finishedWork.stateNode;
	      //触发 getSnapshotBeforeUpdate
          const snapshot = instance.getSnapshotBeforeUpdate(
            finishedWork.elementType === finishedWork.type
              ? prevProps
              : resolveDefaultProps(finishedWork.type, prevProps),
            prevState,
          );
          instance.__reactInternalSnapshotBeforeUpdate = snapshot;
        }
        break;
      }
      case HostRoot: {
    
    
        if (supportsMutation) {
    
    
          const root = finishedWork.stateNode;
          clearContainer(root.containerInfo);
        }
        break;
      }
      case HostComponent:
      case HostText:
      case HostPortal:
      case IncompleteClassComponent:
        break;
      default: {
    
    
        throw new Error(
          'This unit of work tag should not have side-effects. This error is ' +
            'likely caused by a bug in React. Please file an issue.',
        );
      }
    }

    resetCurrentDebugFiberInDEV();
  }
}

MutationEffects

The previous stage just did some preparatory work, and at MutationEffects this stage, we need to synchronize our modifications to our DOM. Let's take a look at what it does:

  • Different types of fibers MutationEffects will be processed differently at this stage, but some common logic will be executed, that is, delete operations and insert operations
  • The first is to call recursivelyTraverseMutationEffectsthe method , which will execute the deletion logic. After the deletion logic is completed,
  • Then there is the call commitReconciliationEffects, which is responsible for inserting DOM nodes into the real DOM tree
  • Finally, we do different operations according to different components
export function commitMutationEffects(
  root: FiberRoot,
  finishedWork: Fiber,
  committedLanes: Lanes,
) {
    
    
  inProgressLanes = committedLanes;
  inProgressRoot = root;
  setCurrentDebugFiberInDEV(finishedWork);//直接进入 commitMutationEffectsOnFiber
  commitMutationEffectsOnFiber(finishedWork, root, committedLanes);
  setCurrentDebugFiberInDEV(finishedWork);
  inProgressLanes = null;
  inProgressRoot = null;
}

function commitMutationEffectsOnFiber(
  finishedWork: Fiber,
  root: FiberRoot,
  lanes: Lanes,
) {
    
    
  // 获取 current 树的内容
  const current = finishedWork.alternate;
  // 获取元素被打上的标记
  const flags = finishedWork.flags;
  // 根据不同类别的元素做不同的擦操作
  switch (finishedWork.tag) {
    
    
    case FunctionComponent:
    case ForwardRef:
    case MemoComponent:
    case SimpleMemoComponent: {
    
    
      // 删除操作
      recursivelyTraverseMutationEffects(root, finishedWork, lanes);
      // 插入操作
      commitReconciliationEffects(finishedWork);
      // ....
    }
    case ClassComponent: {
    
    
      recursivelyTraverseMutationEffects(root, finishedWork, lanes);
      commitReconciliationEffects(finishedWork);
      // ....
      return;
    }
    case HostComponent: {
    
    
      recursivelyTraverseMutationEffects(root, finishedWork, lanes);
      commitReconciliationEffects(finishedWork);
      // ....
      return;
    }
    case HostText: {
    
    
      recursivelyTraverseMutationEffects(root, finishedWork, lanes);
      commitReconciliationEffects(finishedWork);
	  // ....
      return;
    }
    case HostRoot: {
    
    
      recursivelyTraverseMutationEffects(root, finishedWork, lanes);
      commitReconciliationEffects(finishedWork);
      // ....
      return;
    }
	// .... 省略
    default: {
    
    
      recursivelyTraverseMutationEffects(root, finishedWork, lanes);
      commitReconciliationEffects(finishedWork);
      return;
    }
  }
}

Let's first look at the operation recursivelyTraverseMutationEffectsof , which obtains deletionsthe children stored in in the previous stage, and then calls commitDeletionEffectsthe method , which in turn calls commitDeletionEffectsOnFiberthis function. This function needs to be discussed in categories:

  • For native components we do this:
    • First set the element pointed to by its ref to null
    • Traverse down the child nodes and execute the deletion logic
    • Finally call the removeChild method to delete the real DOM
  • For class components
    • First set the element pointed to by its ref to null
    • and then call its componentWillUnmount lifecycle function
    • Traverse the child nodes down and execute the deletion logic. Because it does not correspond to the real DOM, it does not need to be deleted. At the same time, its child nodes are all other components or real native tags, so you only need to delete the child nodes recursively
  • For function components
    • We need to traverse its updateQueue to execute the function returned by the callback function of useInsertionEffect / useLayoutEffect (handling side effects)
    • Traverse down the child nodes and execute the deletion logic
function recursivelyTraverseMutationEffects(
  root: FiberRoot,
  parentFiber: Fiber,
  lanes: Lanes,
) {
    
    
  // 取出要删除的孩子
  const deletions = parentFiber.deletions;
  if (deletions !== null) {
    
    
    for (let i = 0; i < deletions.length; i++) {
    
    
      const childToDelete = deletions[i];
      try {
    
    
        // 删除孩子
        commitDeletionEffects(root, parentFiber, childToDelete);
      } catch (error) {
    
    
        captureCommitPhaseError(childToDelete, parentFiber, error);
      }
    }
  }
}

function commitDeletionEffectsOnFiber(
  finishedRoot: FiberRoot,
  nearestMountedAncestor: Fiber,
  deletedFiber: Fiber,
) {
    
    
  onCommitUnmount(deletedFiber);
  switch (deletedFiber.tag) {
    
    
    case HostComponent: {
    
    
      if (!offscreenSubtreeWasHidden) {
    
    
        // ref 设置回 null
        safelyDetachRef(deletedFiber, nearestMountedAncestor);
      }
    }
    case HostText: {
    
    
      if (supportsMutation) {
    
    
        const prevHostParent = hostParent;
        const prevHostParentIsContainer = hostParentIsContainer;
        hostParent = null;
        // 往下遍历子节点,执行删除
        recursivelyTraverseDeletionEffects(
          finishedRoot,
          nearestMountedAncestor,
          deletedFiber,
        );
        hostParent = prevHostParent;
        hostParentIsContainer = prevHostParentIsContainer;
        // 删除真正的 DOM,调用了原生的 removeChild 方法
        if (hostParent !== null) {
    
    
          if (hostParentIsContainer) {
    
    
            removeChildFromContainer(
              ((hostParent: any): Container),
              (deletedFiber.stateNode: Instance | TextInstance),
            );
          } else {
    
    
            removeChild(
              ((hostParent: any): Instance),
              (deletedFiber.stateNode: Instance | TextInstance),
            );
          }
        }
      } else {
    
    
        recursivelyTraverseDeletionEffects(
          finishedRoot,
          nearestMountedAncestor,
          deletedFiber,
        );
      }
      return;
    }
	// ....
    // 函数组件
    case FunctionComponent:
    case ForwardRef:
    case MemoComponent:
    case SimpleMemoComponent: {
    
    
      if (!offscreenSubtreeWasHidden) {
    
    
        //  读取 updateQueue 队列,队列用链表的方式保存
        const updateQueue: FunctionComponentUpdateQueue | null = (deletedFiber.updateQueue: any);
        if (updateQueue !== null) {
    
    
          const lastEffect = updateQueue.lastEffect;
          if (lastEffect !== null) {
    
    
            const firstEffect = lastEffect.next;
            let effect = firstEffect;
            do {
    
    
              const {
    
    destroy, tag} = effect;
              if (destroy !== undefined) {
    
    
                if ((tag & HookInsertion) !== NoHookEffect) {
    
    
                  // 处理 useInsertionEffect 副作用
                  safelyCallDestroy(
                    deletedFiber,
                    nearestMountedAncestor,
                    destroy,
                  );
                } else if ((tag & HookLayout) !== NoHookEffect) {
    
    
                  // 处理 useLayoutEffect 副作用
                  if (enableSchedulingProfiler) {
    
    
                    markComponentLayoutEffectUnmountStarted(deletedFiber);
                  }
                  if (
                    enableProfilerTimer &&
                    enableProfilerCommitHooks &&
                    deletedFiber.mode & ProfileMode
                  ) {
    
    
                    startLayoutEffectTimer();
                    safelyCallDestroy(
                      deletedFiber,
                      nearestMountedAncestor,
                      destroy,
                    );
                    recordLayoutEffectDuration(deletedFiber);
                  } else {
    
    
                    safelyCallDestroy(
                      deletedFiber,
                      nearestMountedAncestor,
                      destroy,
                    );
                  }
                  if (enableSchedulingProfiler) {
    
    
                    markComponentLayoutEffectUnmountStopped();
                  }
                }
              }
              effect = effect.next;
            } while (effect !== firstEffect);
          }
        }
      }
	  // 遍历子节点执行删除逻辑
      recursivelyTraverseDeletionEffects(
        finishedRoot,
        nearestMountedAncestor,
        deletedFiber,
      );
      return;
    }
    // 类组件
    case ClassComponent: {
    
    
      if (!offscreenSubtreeWasHidden) {
    
    
        // 移除 ref
        safelyDetachRef(deletedFiber, nearestMountedAncestor);
        const instance = deletedFiber.stateNode;
        if (typeof instance.componentWillUnmount === 'function') {
    
    
          // 调用类组件实例的 componentWillUnmount 方法
          safelyCallComponentWillUnmount(
            deletedFiber,
            nearestMountedAncestor,
            instance,
          );
        }
      }
      //遍历子节点执行删除逻辑
      recursivelyTraverseDeletionEffects(
        finishedRoot,
        nearestMountedAncestor,
        deletedFiber,
      );
      return;
    }
	// ....省略
    default: {
    
    
      recursivelyTraverseDeletionEffects(
        finishedRoot,
        nearestMountedAncestor,
        deletedFiber,
      );
      return;
    }
  }
}

Then let's take a look at another insertion logic commitReconciliationEffects, which only operates on native components and fiber root nodes, and does not operate on function components and class components. Its logic is:

  • Determine whether the parent Fiber of the node needs to be reset, and if so, call parent.textContent = ''to reset its content
  • Then find out whether it has siblings, if so, call insertBefore the method to insert the content before the sibling node, if not, call the parent node's appendChild to insert
function commitReconciliationEffects(finishedWork: Fiber) {
    
    
  const flags = finishedWork.flags;
  if (flags & Placement) {
    
    
    try {
    
    
      //执行操作
      commitPlacement(finishedWork);
    } catch (error) {
    
    
      captureCommitPhaseError(finishedWork, finishedWork.return, error);
    }
    // 移除 Placement 标志
    finishedWork.flags &= ~Placement;
  }
  if (flags & Hydrating) {
    
    
    finishedWork.flags &= ~Hydrating;
  }
}

function commitPlacement(finishedWork: Fiber): void {
    
    
  if (!supportsMutation) {
    
    
    return;
  }
  //获取父 fiber
  const parentFiber = getHostParentFiber(finishedWork);
  switch (parentFiber.tag) {
    
    
    case HostComponent: {
    
    
      const parent: Instance = parentFiber.stateNode;
      //  判断父 fiber 是否有 ContentReset(内容重置)标记
      if (parentFiber.flags & ContentReset) {
    
    
        // 通过 parent.textContent = '' 的方式重置
        resetTextContent(parent);
        parentFiber.flags &= ~ContentReset;
      }
      // 找它的下一个兄弟 DOM 节点,
      const before = getHostSibling(finishedWork);
      // 如果存在,用 insertBefore 方法;如果没有,就调用原生的 appendChild 方法
      insertOrAppendPlacementNode(finishedWork, before, parent);
      break;
    }
    case HostRoot:
    case HostPortal: {
    
    
      const parent: Container = parentFiber.stateNode.containerInfo;
      const before = getHostSibling(finishedWork);
      insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
      break;
    }
    default:
      throw new Error(
        'Invalid host parent fiber. This error is likely caused by a bug ' +
          'in React. Please file an issue.',
      );
  }
}

Now let's go back to ours commitMutationEffectsOnFiber, let's take a look at what the three different nodes did after the deletion and insertion operations:

  • For our class components, we don't handle
  • For our function components we handle side effects of useInsertionEffect, useLayoutEffect
  • For native components, if they can be reused, we call to commitUpdateexecute the update logic, otherwise directly reset the native node
  • If it is native text, we call commitTextUpdatethe function to update its text content
  switch (finishedWork.tag) {
    
    
    case FunctionComponent:
    case ForwardRef:
    case MemoComponent:
    case SimpleMemoComponent: {
    
    
	  // ....
      if (flags & Update) {
    
    
        try {
    
    
          // 找出 useInsertionEffect 的 destroy 方法去调用
          commitHookEffectListUnmount(
            HookInsertion | HookHasEffect,
            finishedWork,
            finishedWork.return,
          );
          // 执行 useInsertionEffect 的回调函数,并将返回值保存到 effect.destory 里。
          commitHookEffectListMount(
            HookInsertion | HookHasEffect,
            finishedWork,
          );
        } catch (error) {
    
    
          captureCommitPhaseError(finishedWork, finishedWork.return, error);
        }
        if (
          enableProfilerTimer &&
          enableProfilerCommitHooks &&
          finishedWork.mode & ProfileMode
        ) {
    
    
          try {
    
    
            startLayoutEffectTimer();
            // 执行 useLayoutEffect 对应的 destroy 方法
            commitHookEffectListUnmount(
              HookLayout | HookHasEffect,
              finishedWork,
              finishedWork.return,
            );
          } catch (error) {
    
    
            captureCommitPhaseError(finishedWork, finishedWork.return, error);
          }
          recordLayoutEffectDuration(finishedWork);
        } else {
    
    
          try {
    
    
            commitHookEffectListUnmount(
              HookLayout | HookHasEffect,
              finishedWork,
              finishedWork.return,
            );
          } catch (error) {
    
    
            captureCommitPhaseError(finishedWork, finishedWork.return, error);
          }
        }
      }
      return;
    }
    // 类组件不会进行操作
    case ClassComponent: {
    
    
      // ...
      if (flags & Ref) {
    
    
        if (current !== null) {
    
    
          safelyDetachRef(current, current.return);
        }
      }
      return;
    }
    //原生组件
    case HostComponent: {
    
    
      //...
      if (flags & Ref) {
    
    
        if (current !== null) {
    
    
          safelyDetachRef(current, current.return);
        }
      }
      if (supportsMutation) {
    
    
        // 判断是不是需要重置
        if (finishedWork.flags & ContentReset) {
    
    
          const instance: Instance = finishedWork.stateNode;
          try {
    
    
            resetTextContent(instance);
          } catch (error) {
    
    
            captureCommitPhaseError(finishedWork, finishedWork.return, error);
          }
        }
		// 判断是不是需要更新
        if (flags & Update) {
    
    
          const instance: Instance = finishedWork.stateNode;
          if (instance != null) {
    
    
            const newProps = finishedWork.memoizedProps;
            const oldProps =
              current !== null ? current.memoizedProps : newProps;
            const type = finishedWork.type;
            const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
            finishedWork.updateQueue = null;
            if (updatePayload !== null) {
    
    
              try {
    
    
                // 更新操作
                commitUpdate(
                  instance,
                  updatePayload,
                  type,
                  oldProps,
                  newProps,
                  finishedWork,
                );
              } catch (error) {
    
    
                captureCommitPhaseError(
                  finishedWork,
                  finishedWork.return,
                  error,
                );
              }
            }
          }
        }
      }
      return;
    }
    //原生文本
    case HostText: {
    
    
	  //....
      if (flags & Update) {
    
    
        if (supportsMutation) {
    
    
          if (finishedWork.stateNode === null) {
    
    
            throw new Error(
              'This should have a text node initialized. This error is likely ' +
                'caused by a bug in React. Please file an issue.',
            );
          }

          const textInstance: TextInstance = finishedWork.stateNode;
          const newText: string = finishedWork.memoizedProps;
          const oldText: string =
            current !== null ? current.memoizedProps : newText;
          try {
    
    
            // 更新操作
            commitTextUpdate(textInstance, oldText, newText);
          } catch (error) {
    
    
            captureCommitPhaseError(finishedWork, finishedWork.return, error);
          }
        }
      }
      return;
    }
    //...
}

Finally, let's take a look at this update function. Its logic is very simple: it obtains the real dom node instance, props and updateQueue, and applies the properties of props to the real DOM one by one

export function commitUpdate(
  domElement: Instance,
  updatePayload: Array<mixed>,
  type: string,
  oldProps: Props,
  newProps: Props,
  internalInstanceHandle: Object,
): void {
    
    
  // 对 props 的进行对比更新
  updateProperties(domElement, updatePayload, type, oldProps, newProps);
  // 应用更新
  updateFiberProps(domElement, newProps);
}
// 对比更新
export function updateProperties(
  domElement: Element,
  updatePayload: Array<any>,
  tag: string,
  lastRawProps: Object,
  nextRawProps: Object,
): void {
    
    
  // 针对表单组件进行特殊处理
  if (
    tag === 'input' &&
    nextRawProps.type === 'radio' &&
    nextRawProps.name != null
  ) {
    
    
    ReactDOMInputUpdateChecked(domElement, nextRawProps);
  }
  // 判断是否为用户自定义的组件,即是否包含 "-"
  const wasCustomComponentTag = isCustomComponent(tag, lastRawProps);
  const isCustomComponentTag = isCustomComponent(tag, nextRawProps);
  // 将 diff 结果应用于真实 dom
  updateDOMProperties(
    domElement,
    updatePayload,
    wasCustomComponentTag,
    isCustomComponentTag,
  );
  // 针对表单的特殊处理
  switch (tag) {
    
    
    case 'input':
      ReactDOMInputUpdateWrapper(domElement, nextRawProps);
      break;
    case 'textarea':
      ReactDOMTextareaUpdateWrapper(domElement, nextRawProps);
      break;
    case 'select':
      ReactDOMSelectPostUpdateWrapper(domElement, nextRawProps);
      break;
  }
}

function updateDOMProperties(
  domElement: Element,
  updatePayload: Array<any>,
  wasCustomComponentTag: boolean,
  isCustomComponentTag: boolean,
): void {
    
    
  // 对 updatePayload 遍历
  for (let i = 0; i < updatePayload.length; i += 2) {
    
    
    const propKey = updatePayload[i];
    const propValue = updatePayload[i + 1];
    if (propKey === STYLE) {
    
    
      // 处理 style 样式更新
      setValueForStyles(domElement, propValue);
    } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
    
    
      // 处理 innerHTML 改变
      setInnerHTML(domElement, propValue);
    } else if (propKey === CHILDREN) {
    
    
      // 处理 textContent
      setTextContent(domElement, propValue);
    } else {
    
    
      // 处理其他节点属性
      setValueForProperty(domElement, propKey, propValue, isCustomComponentTag);
    }
  }
}

LayoutEffects

Finally, let's take a look at LayoutEffectsthis stage. At this stage, we can already call our real DOM. It is mainly about execution componentDidMountand componentDidUpdatelife cycle. Let's take a look at the logic:

  • Its traversal process is BeforeMutationEffectsbasically the same as that of . They all use the depth-first traversal method, we will not elaborate here, let's look directly at commitLayoutEffectOnFiberthis function
  • It is still processed in components:
    • If it is a function component, we can execute its useLayoutEffecthooks
    • If it is a class component, we need to execute its componentDidMountor componentDidUpdatelife cycle, and then we have to deal with the callback function commitUpdateQueuein
    • If the root node, we need to handle its callback, which is ReactDOM.renderthe callback function of
    • If it is a native component, we handle its automatic update
  • commitUpdateQueueThe function will traverse the effects on the finishedQueue, and if there is a callback, execute the callback. At the same time, the effects on the finishedQueue will be reset to null
function commitLayoutEffectOnFiber(
  finishedRoot: FiberRoot,
  current: Fiber | null,
  finishedWork: Fiber,
  committedLanes: Lanes,
): void {
    
    
  if ((finishedWork.flags & LayoutMask) !== NoFlags) {
    
    
    switch (finishedWork.tag) {
    
    
      case FunctionComponent:
      case ForwardRef:
      case SimpleMemoComponent: {
    
    
        if (
          !enableSuspenseLayoutEffectSemantics ||
          !offscreenSubtreeWasHidden
        ) {
    
    
          if (
            enableProfilerTimer &&
            enableProfilerCommitHooks &&
            finishedWork.mode & ProfileMode
          ) {
    
    
            try {
    
    
              startLayoutEffectTimer();
              // 提交 useLayoutEffect 
              commitHookEffectListMount(
                HookLayout | HookHasEffect,
                finishedWork,
              );
            } finally {
    
    
              recordLayoutEffectDuration(finishedWork);
            }
          } else {
    
    
            commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
          }
        }
        break;
      }
      case ClassComponent: {
    
    
        const instance = finishedWork.stateNode;
        if (finishedWork.flags & Update) {
    
    
          if (!offscreenSubtreeWasHidden) {
    
    
            if (current === null) {
    
    
              if (
                enableProfilerTimer &&
                enableProfilerCommitHooks &&
                finishedWork.mode & ProfileMode
              ) {
    
    
                try {
    
    
                  startLayoutEffectTimer();
                  // 首次渲染,触发 componentDidMount 生命周期
                  instance.componentDidMount();
                } finally {
    
    
                  recordLayoutEffectDuration(finishedWork);
                }
              } else {
    
    
                instance.componentDidMount();
              }
            } else {
    
    
              const prevProps =
                finishedWork.elementType === finishedWork.type
                  ? current.memoizedProps
                  : resolveDefaultProps(
                      finishedWork.type,
                      current.memoizedProps,
                    );
              const prevState = current.memoizedState;
              if (
                enableProfilerTimer &&
                enableProfilerCommitHooks &&
                finishedWork.mode & ProfileMode
              ) {
    
    
                try {
    
    
                  startLayoutEffectTimer();
                  // 非首次渲染,触发 componentDidUpdate 生命周期
                  instance.componentDidUpdate(
                    prevProps,
                    prevState,
                    instance.__reactInternalSnapshotBeforeUpdate,
                  );
                } finally {
    
    
                  recordLayoutEffectDuration(finishedWork);
                }
              } else {
    
    
                instance.componentDidUpdate(
                  prevProps,
                  prevState,
                  instance.__reactInternalSnapshotBeforeUpdate,
                );
              }
            }
          }
        }

        const updateQueue: UpdateQueue<
          *,
        > | null = (finishedWork.updateQueue: any);
        if (updateQueue !== null) {
    
    
          // 执行 commitUpdateQueue 处理回调
          commitUpdateQueue(finishedWork, updateQueue, instance);
        }
        break;
      }
      case HostRoot: {
    
    
        const updateQueue: UpdateQueue<
          *,
        > | null = (finishedWork.updateQueue: any);
        if (updateQueue !== null) {
    
    
          let instance = null;
          if (finishedWork.child !== null) {
    
    
            switch (finishedWork.child.tag) {
    
    
              case HostComponent:
                instance = getPublicInstance(finishedWork.child.stateNode);
                break;
              case ClassComponent:
                instance = finishedWork.child.stateNode;
                break;
            }
          }
          //  调用 commitUpdateQueue 处理 ReactDOM.render 的回调
          commitUpdateQueue(finishedWork, updateQueue, instance);
        }
        break;
      }
      case HostComponent: {
    
    
        const instance: Instance = finishedWork.stateNode;
        // commitMount 处理 input 标签有 auto-focus 的情况
        if (current === null && finishedWork.flags & Update) {
    
    
          const type = finishedWork.type;
          const props = finishedWork.memoizedProps;
          commitMount(instance, type, props, finishedWork);
        }

        break;
      }
      case HostText: 
        break;
      }
      case HostPortal: {
    
    
        break;
      }
      default:
        throw new Error(
          'This unit of work tag should not have side-effects. This error is ' +
            'likely caused by a bug in React. Please file an issue.',
        );
    }
  }
}

Summarize

The above is the content of our commit stage. Let’s summarize its operation process, which can be roughly divided into five stages.

  • Before entering BeforeMutationEffectsthe function, we first process useEffect related tasks and do some initialization work
  • After entering BeforeMutationEffectsthe stage , we traverse our Fiber tree in depth first, process focusthe and blurlogic after DOM node rendering/deletion, and then process getSnapshotBeforeUpdate the life
  • Then we enter MutationEffectsthe stage . In this stage, we execute recursivelyTraverseMutationEffectsthe method to process the nodes that need to be deleted, and then execute commitReconciliationEffectsthe logic to insert into the DOM, and then process them separately
    • For our function components we handle side effects of useInsertionEffect, useLayoutEffect
    • For native components and text, if calling commitUpdateexecute update logic (reuse)
  • Finally, we enter LayoutEffectsthe stage , mainly to execute the hooks of class components componentDidMountand componentDidUpdatelife cycle or function components useLayoutEffect, and then process the callbacks of the effects on the finishedQueue
  • After the end of the three phases, process the useEffect relevant , and then execute the synchronization tasks related to the commit phase

After the above operations, the React nodes are synchronized from the Fiber we built to the real DOM and displayed to the user, and the related life cycle and Hooks are also triggered. So far, a React rendering process starting from jsx has been finished. We will sort it out completely after we finish talking about scheduling.

Now let's take a look at what is still unresolved:

  • In the previous explanation, we skipped the content related to priority and scheduling by default. This is actually a very important stage. After that, we schedulerwill combine our previous explanation of the rendering process to explain schedulerhow arrange tasks and schedule processes. of
  • We have mentioned part of the content of hooks, but we haven't systematically talked about how hooks work. We will open a few separate articles later to talk about how our hooks are mounted and take effect

Then we will answer these two questions in the following tutorials.

Guess you like

Origin blog.csdn.net/weixin_46463785/article/details/130151495