React source code read – render

What is the render phase

Rendering is done by the Renderer. This phase is called the Commit phase, which will render the submitted information on the page. This is where we “coordinated” Fiber for a long time in the previous phase.

The basic flow

Procedurally, the COMMIT phase can be divided into three phases:

  • Before mutation stage, in which the DOM node has not been rendered to the interface, is triggered during the mutation stagegetSnapshotBeforeUpdateThe useEffect hook related scheduling logic is also handled
  • Mutation stage, which is responsible for rendering DOM nodes, will perform different DOM operations during rendering according to flags of coordination stage
  • The Layout stage, which handles the finishing logic after DOM rendering is complete, such as:componentDidMount/componentDidUpdate, the calluseLayoutEffectHook function callback, etc., will also putfiberRootcurrentPointer toworkInProgress Fiber

Source code analysis

Like the coordination phase, the render phase starts from the performSyncWorkOnRoot function. The key call here is commitRoot, and the input parameter root is The FiberRootNode processed in the coordination phase, which is the root Fiber of the entire application. FinishedWork is the Work In Progress built In memory during the coordination phase.

/ / path: packages/react - the reconciler/SRC/ReactFiberWorkLoop. New. Js
function performSyncWorkOnRoot(root) {
  // Omit some code

  if (exitStatus === RootFatalErrored) {
    const fatalError = workInProgressRootFatalError;
    // Coordination phase
    prepareFreshStack(root, NoLanes);
    markRootSuspended(root, lanes);
    ensureRootIsScheduled(root, now());
    throw fatalError;
  }

  const finishedWork: Fiber = (root.current.alternate: any);
  root.finishedWork = finishedWork;
  root.finishedLanes = lanes;
  // Render phase
  commitRoot(root);

  ensureRootIsScheduled(root, now());

  return null;
}
Copy the code

CommitRoot calls the commitRootImpl function, which is divided into three stages, as described in the basic flow.

The overall call process is shown in the figure below:

Before entering the before mutation phase, there is an important call, as shown in the following code:

/ / path: packages/react - the reconciler/SRC/ReactFiberWorkLoop. New. Js
function commitRootImpl(root, renderPriorityLevel) {
  do {
    flushPassiveEffects();
  } while(rootWithPendingPassiveEffects ! = =null);
  // Omit some code

  // Get the latest fiber processed in memory
  const finishedWork = root.finishedWork;
    
  // Omit some code
  if( (finishedWork.subtreeFlags & PassiveMask) ! == NoFlags || (finishedWork.flags & PassiveMask) ! == NoFlags ) {if(! rootDoesHavePassiveEffects) { rootDoesHavePassiveEffects =true;
      // Call useEffect asynchronously
      scheduleCallback(NormalSchedulerPriority, () = > {
        flushPassiveEffects();
        return null; }); }}// Omit some code
  if (subtreeHasEffects || rootHasEffect) {
    // Omit some code

    const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
      root,
      finishedWork,
    );

    // Omit some code
}
Copy the code

ScheduleCallback asynchronously calls flushPassiveEffects (page rendering is completed). The call stack of this function is also very deep, so we will not elaborate on it here. Finally, useEffect is called.

before mutation

As an important portal commitBeforeMutationEffects is before mutation stages.

/ / path: packages/react - the reconciler/SRC/ReactFiberCommitWork. New. Js
export function commitBeforeMutationEffects(root: FiberRoot, firstChild: Fiber,) {
  focusedInstanceHandle = prepareForCommit(root.containerInfo);

  nextEffect = firstChild;
  commitBeforeMutationEffects_begin();

  // We no longer need to track the active instance fiber
  const shouldFire = shouldFireAfterActiveInstanceBlur;
  shouldFireAfterActiveInstanceBlur = false;
  focusedInstanceHandle = null;

  return shouldFire;
}
Copy the code

You can see that nextEffect is assigned here, which is the starting point of all Fiber, and when nextEffect is ready, you’re ready to use it. Then call the commitBeforeMutationEffects_begin function.

/ / path: packages/react - the reconciler/SRC/ReactFiberCommitWork. New. Js
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++) {
          constdeletion = deletions[i]; commitBeforeMutationEffectsDeletion(deletion); }}}const child = fiber.child;
    if( (fiber.subtreeFlags & BeforeMutationMask) ! == NoFlags && child ! = =null
    ) {
      ensureCorrectReturnPointer(child, fiber);
      nextEffect = child;
    } else{ commitBeforeMutationEffects_complete(); }}}function commitBeforeMutationEffects_complete() {
  while(nextEffect ! = =null) {
    const fiber = nextEffect;
    setCurrentDebugFiberInDEV(fiber);
    try {
      commitBeforeMutationEffectsOnFiber(fiber);
    } catch (error) {
      reportUncaughtErrorInDEV(error);
      captureCommitPhaseError(fiber, fiber.return, error);
    }
    resetCurrentDebugFiberInDEV();

    const sibling = fiber.sibling;
    if(sibling ! = =null) {
      ensureCorrectReturnPointer(sibling, fiber.return);
      nextEffect = sibling;
      return; } nextEffect = fiber.return; }}Copy the code

The call and coordination phase is similar to that of begin, which is depth-first traversal of the Child node and then complete traversal of the Sibling node. CommitBeforeMutationEffects_complete function will be called commitBeforeMutationEffectsOnFiber function, this function has a heap of the switch… case… Statement, the class component calls the getSnapshotBeforeUpdate function.

mutation

/ / path: packages/react - the reconciler/SRC/ReactFiberWorkLoop. New. Js
function commitRootImpl(root, renderPriorityLevel) {
  // Omit some code
  if (subtreeHasEffects || rootHasEffect) {
    // Omit some code

    const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
      root,
      finishedWork,
    );
      
    // Omit some code
    commitMutationEffects(root, finishedWork, lanes);
    // Omit some code
  }
  // Omit some code
}
Copy the code

The entry function for the mutation stage is commitMutationEffects.

  • Root is FiberRoot and is the rootFiber
  • FinishedWork is of type Fiber, which was dealt with in the previous phase
  • ‘Lanes’ refers to’ lanes’
/ / path: packages/react - the reconciler/SRC/ReactFiberCommitWork. New. Js
export function commitMutationEffects(root: FiberRoot, firstChild: Fiber, committedLanes: Lanes,) {
  inProgressLanes = committedLanes;
  inProgressRoot = root;
  // Used to drive the starting point of the loop, commitMutationEffects_begin
  nextEffect = firstChild;

  commitMutationEffects_begin(root);

  inProgressLanes = null;
  inProgressRoot = null;
}
Copy the code

CommitMutationEffects calls commitMutationEffects_begin, which calls commitMutationEffects_complete.

/ / path: packages/react - the reconciler/SRC/ReactFiberCommitWork. New. Js
function commitMutationEffects_begin(root: FiberRoot) {
  while(nextEffect ! = =null) {
    const fiber = nextEffect;

    const deletions = fiber.deletions;
    if(deletions ! = =null) {
      for (let i = 0; i < deletions.length; i++) {
        const childToDelete = deletions[i];
        try {
          commitDeletion(root, childToDelete, fiber);
        } catch(error) { reportUncaughtErrorInDEV(error); captureCommitPhaseError(childToDelete, fiber, error); }}}const child = fiber.child;
    if((fiber.subtreeFlags & MutationMask) ! == NoFlags && child ! = =null) {
      ensureCorrectReturnPointer(child, fiber);
      nextEffect = child;
    } else{ commitMutationEffects_complete(root); }}}function commitMutationEffects_complete(root: FiberRoot) {
  while(nextEffect ! = =null) {
    const fiber = nextEffect;
    setCurrentDebugFiberInDEV(fiber);
    try {
      commitMutationEffectsOnFiber(fiber, root);
    } catch (error) {
      reportUncaughtErrorInDEV(error);
      captureCommitPhaseError(fiber, fiber.return, error);
    }
    resetCurrentDebugFiberInDEV();

    const sibling = fiber.sibling;
    if(sibling ! = =null) {
      ensureCorrectReturnPointer(sibling, fiber.return);
      nextEffect = sibling;
      return; } nextEffect = fiber.return; }}Copy the code

These two functions do actually do is similar as in the previous stage, is the first child node of the tree traversal Fiber, brother to traverse the nodes, commitMutationEffects_complete again call commitMutationEffectsOnFiber, Process will deal with all kinds of tag (Placement | Update | Deletion | Hydrating), Hydrating is rendering of a service, not to consider first. The previous several insert, update, and delete operations respectively.

Enter commitMutationEffects_begin to check whether the DELEtions flag needs to be called with the commitDeletion flag

/ / path: packages/react - the reconciler/SRC/ReactFiberCommitWork. New. Js
function commitDeletion(finishedRoot: FiberRoot, current: Fiber, nearestMountedAncestor: Fiber,) :void {
  if (supportsMutation) {
    unmountHostComponents(finishedRoot, current, nearestMountedAncestor);
  } else {
    commitNestedUnmounts(finishedRoot, current, nearestMountedAncestor);
  }

  detachFiberMutation(current);
}
Copy the code

CommitDeletion performs the following operations:

  1. Recursive callsFiberNodes and their descendantsFiberNode,fiber.tagClassComponentWill callcomponentWillUnmountLifecycle hook, removed from the pageFiberCorresponding DOM node
  2. unbundlingref
  3. schedulinguseEffectDestruct function of
/ / path: packages/react - the reconciler/SRC/ReactFiberCommitWork. New. Js
function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) {
  const flags = finishedWork.flags;
  
  // Omit some code
  // Reset the text node according to ContentReset Flags
  if (flags & ContentReset) {
    commitResetTextContent(finishedWork);
  }

  / / update the ref
  if (flags & Ref) {
    const current = finishedWork.alternate;
    if(current ! = =null) {
      commitDetachRef(current);
    }
    if (enableScopeAPI) {
      if(finishedWork.tag === ScopeComponent) { commitAttachRef(finishedWork); }}}const primaryFlags = flags & (Placement | Update | Hydrating);
  outer: switch (primaryFlags) {
    case Placement: {
      commitPlacement(finishedWork);
      finishedWork.flags &= ~Placement;
      break;
    }
    case PlacementAndUpdate: {
      // Placement
      commitPlacement(finishedWork);
      finishedWork.flags &= ~Placement;

      // Update
      const current = finishedWork.alternate;
      commitWork(current, finishedWork);
      break;
    }
    // SSR
    case Hydrating: {
      finishedWork.flags &= ~Hydrating;
      break;
    }
    // SSR
    case HydratingAndUpdate: {
      finishedWork.flags &= ~Hydrating;

      // Update
      const current = finishedWork.alternate;
      commitWork(current, finishedWork);
      break;
    }
    / / remove the DOM
    case Update: {
      const current = finishedWork.alternate;
      commitWork(current, finishedWork);
      break; }}}Copy the code

CommitMutationEffectsOnFiber function do basically mutation stages mainly do:

  1. commitResetTextContentAnd finally calledsetTextContentReset the text type node
  2. commitDetachRef,commitAttachRefupdateref
  3. A pile of the switch… case… To deal withflagsAnd then callcommitPlacement commitWork

Let’s take a look at what commitPlacement and commitWork do.

/ / path: packages/react - the reconciler/SRC/ReactFiberCommitWork. New. Js
function commitPlacement(finishedWork: Fiber) :void {
  if(! supportsMutation) {return;
  }

  const parentFiber = getHostParentFiber(finishedWork);

  let parent;
  let isContainer;
  const parentStateNode = parentFiber.stateNode;
  switch (parentFiber.tag) {
    case HostComponent:
      parent = parentStateNode;
      isContainer = false;
      break;
    case HostRoot:
      parent = parentStateNode.containerInfo;
      isContainer = true;
      break;
    case HostPortal:
      parent = parentStateNode.containerInfo;
      isContainer = true;
      break;
    default:
      invariant(
        false.'Invalid host parent fiber. This error is likely caused by a bug ' +
          'in React. Please file an issue.',); }if (parentFiber.flags & ContentReset) {
    resetTextContent(parent);
    parentFiber.flags &= ~ContentReset;
  }

  const before = getHostSibling(finishedWork);
  // parentStateNode Is rootFiber
  if (isContainer) {
    insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
  } else{ insertOrAppendPlacementNode(finishedWork, before, parent); }}Copy the code

The commitPlacement workflow is:

  1. getHostParentFiberGet the parent DOM node
  2. getHostSiblingGet sibling DOM nodes
  3. insertOrAppendPlacementNodeIntoContainerorinsertOrAppendPlacementNodeThe insert or append node is called depending on whether the DOM sibling exists
/ / path: packages/react - the reconciler/SRC/ReactFiberCommitWork. New. Js
function commitWork(current: Fiber | null, finishedWork: Fiber) :void {
  if(! supportsMutation) {// Omit some code
  }

  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case MemoComponent:
    case SimpleMemoComponent: {
      if (
        enableProfilerTimer &&
        enableProfilerCommitHooks &&
        finishedWork.mode & ProfileMode
      ) {
        try {
          startLayoutEffectTimer();
          commitHookEffectListUnmount(
            HookLayout | HookHasEffect,
            finishedWork,
            finishedWork.return,
          );
        } finally{ recordLayoutEffectDuration(finishedWork); }}else {
        commitHookEffectListUnmount(
          HookLayout | HookHasEffect,
          finishedWork,
          finishedWork.return,
        );
      }
      return;
    }
    case ClassComponent: {
      return;
    }
    case HostComponent: {
      const instance: Instance = finishedWork.stateNode;
      if(instance ! =null) {
        const newProps = finishedWork.memoizedProps;
        constoldProps = current ! = =null ? current.memoizedProps : newProps;
        const type = finishedWork.type;
        const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
        finishedWork.updateQueue = null;
        if(updatePayload ! = =null) { commitUpdate( instance, updatePayload, type, oldProps, newProps, finishedWork, ); }}return;
    }
    caseHostText: { invariant( finishedWork.stateNode ! = =null.'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;
      constoldText: string = current ! = =null ? current.memoizedProps : newText;
      commitTextUpdate(textInstance, oldText, newText);
      return;
    }
    case HostRoot: {
      if (supportsHydration) {
        const root: FiberRoot = finishedWork.stateNode;
        if (root.hydrate) {
          root.hydrate = false; commitHydratedContainer(root.containerInfo); }}return;
    }
    // Omit some code
  }
  // Omit some code
}
Copy the code

CommitWork executes different code, depending on the tag type, such as:

  • FunctionComponent, the FunctionComponent: callscommitHookEffectListUnmount, the implementation ofuseLayoutEffectThe hook destruction function
  • The HostComponent, which is the native DOM node, is calledcommitUpdateFunction to update DOM node attributes

layout

/ / path: packages/react - the reconciler/SRC/ReactFiberWorkLoop. New. Js
function commitRootImpl(root, renderPriorityLevel) {
  // Omit some code
  if (subtreeHasEffects || rootHasEffect) {
    // Omit some code

    commitLayoutEffects(finishedWork, root, lanes);

    // Omit some code
}
Copy the code

Commitlayout ects is the entry point to the layout phase.

/ / path: packages/react - the reconciler/SRC/ReactFiberCommitWork. New. Js
export function commitLayoutEffects(finishedWork: Fiber, root: FiberRoot, committedLanes: Lanes,) :void {
  inProgressLanes = committedLanes;
  inProgressRoot = root;
  nextEffect = finishedWork;

  commitLayoutEffects_begin(finishedWork, root, committedLanes);

  inProgressLanes = null;
  inProgressRoot = null;
}
Copy the code

This function does the same thing we did in the previous stage: First iterates through the child node of the Fiber tree, then iterates through its siblings. CommitLayoutEffects_begin calls commitLayoutEffects_complete. CommitLayoutEffects_complete again call commitLayoutEffectsOnFiber function, this function code is also more.

/ / path: packages/react - the reconciler/SRC/ReactFiberCommitWork. New. Js
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();
              // Execute the useLayoutEffect callback function
              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) {
              // Omit some code
              if (
                enableProfilerTimer &&
                enableProfilerCommitHooks &&
                finishedWork.mode & ProfileMode
              ) {
                try {
                  startLayoutEffectTimer();
                  // componentDidMout Lifecycle hook
                  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;
              // Omit some code
              if (
                enableProfilerTimer &&
                enableProfilerCommitHooks &&
                finishedWork.mode & ProfileMode
              ) {
                try {
                  startLayoutEffectTimer();
                  // componentDidUpdate Life cycle hook
                  instance.componentDidUpdate(
                    prevProps,
                    prevState,
                    instance.__reactInternalSnapshotBeforeUpdate,
                  );
                } finally{ recordLayoutEffectDuration(finishedWork); }}else{ instance.componentDidUpdate( prevProps, prevState, instance.__reactInternalSnapshotBeforeUpdate, ); }}}}// Omit some code
      }
      // Omit some code}}if(! enableSuspenseLayoutEffectSemantics || ! offscreenSubtreeWasHidden) {if (enableScopeAPI) {
      if(finishedWork.flags & Ref && finishedWork.tag ! == ScopeComponent) {/ / assignment refcommitAttachRef(finishedWork); }}else {
      if(finishedWork.flags & Ref) { commitAttachRef(finishedWork); }}}}Copy the code

CommitLayoutEffectsOnFiber function mainly do:

  1. Of a function component and related typeuseLayoutEffectThe callback function of;
  2. Calling the class componentcomponentDidMountcomponentDidUpdateLifecycle hooks.

conclusion

  1. First, inbefore mutationBefore, it was called asynchronouslyuseEffect.
  2. inbefore mutationPhase, which calls the class componentgetSnapshotBeforeUpdateLifecycle hooks.
  3. mutationWhen a delete flag is encountered, the class component’scomponentWillUnmountLifecycle hook, removed from the pageFiberCorresponding DOM node; unbundlingref; schedulinguseEffectDestruct function of.
  4. mutationPhase, encountered a non-delete tag, reset the text type node; updateref; calluseLayoutEffectDestruct function of; Performs native DOM node property updates.
  5. layoutPhase, calluseLayoutEffectA hook; Calling the class componentcomponentDidMountcomponentDidUpdateLifecycle hooks.

As can be seen from the above steps, useLayoutEffect is executed synchronously after the DOM node is updated. Because useEffect is the cause of asynchronous invocation, useLayoutEffect is executed earlier than useEffect.

Also, focus on this code:

root.current = finishedWork;
Copy the code

It is executed between the mutation and layout phases to switch the Current Fiber tree, where the workInProgress Fiber tree becomes the Current Fiber tree.