Commit Phase Overview

Fiber’s DOM structure and EffectList are also successfully mounted on Fiber after the render phase is completed. After the Render phase is complete, it’s time for Fiber’s commit phase.

function performConcurrentWorkOnRoot(root, didTimeout) {
    // Render phase is complete and ready to enter commit phase
    root.finishedWork = finishedWork;
    root.finishedLanes = lanes;
    finishConcurrentRender(root, exitStatus, lanes);
}

function finishConcurrentRender(root, exitStatus, lanes) {
  switch (exitStatus) {
    case RootCompleted: {
      // The work completed. Ready to commit.
      commitRoot(root);
      break; }}}Copy the code

CommitRoot is the start point of the commit phase, which is synchronous logic and does not use the time sharding function in concurrent mode. The main flow of commitRoot is as follows

Commit phase details

Handles the Effect from the last render

function commitRootImpl(root, renderPriorityLevel) {
   do {
      flushPassiveEffects();
   } while(rootWithPendingPassiveEffects ! = =null);
 }
Copy the code

The start time of the COMMIT phase will process the effect asynchronous handlers registered in the last Render phase & which have not yet been completed. The while loop ensures that if a new setState is executed in an asynchronous function, it is completed before the COMMIT.

Mount the asynchronous callback function for Effect

If the Root Fiber subtreeFlags or flags is not NoFlags, the Root Fiber Effect needs to be executed. Execute the flushPassiveEffects to register the asynchronous callback function.

The processing logic for subtreeFlags is in the completeWork phase of the Render phase.

  if( (finishedWork.subtreeFlags & PassiveMask) ! == NoFlags || (finishedWork.flags & PassiveMask) ! == NoFlags ) {if(! rootDoesHavePassiveEffects) { rootDoesHavePassiveEffects =true;
      pendingPassiveEffectsRemainingLanes = remainingLanes;
      scheduleCallback(NormalSchedulerPriority, () = > {
        flushPassiveEffects();
        return null; }); }}Copy the code

The focus is on the flushPassiveEffects function, which is already called when processing the Effect generated by the last render. Its role is to handle the create/destory logic of useEffect.

export function flushPassiveEffects() :boolean {
      return flushPassiveEffectsImpl();
}

function flushPassiveEffectsImpl() {
  // Handle the destory function of hook
  commitPassiveUnmountEffects(root.current);
  // Handle the create function of the hook
  commitPassiveMountEffects(root, root.current);
}
Copy the code

CommitPassiveUnmountEffects and commitPassiveMountEffect logical comparison is consistent, depth-first traversal, and at the begin and complete implementation of the different logical, Here commitPassiveUnmountEffects as examples.

function commitPassiveUnmountEffects_begin() {
  while(nextEffect ! = =null) {
    const fiber = nextEffect;
    const child = fiber.child;
    if((fiber.subtreeFlags & PassiveMask) ! == NoFlags && child ! = =null) {
      ensureCorrectReturnPointer(child, fiber);
      nextEffect = child;
    } else{ commitPassiveUnmountEffects_complete(); }}}function commitPassiveUnmountEffects_complete() {
  while(nextEffect ! = =null) {
    const fiber = nextEffect;
    if((fiber.flags & Passive) ! == NoFlags) { commitPassiveUnmountOnFiber(fiber); }const sibling = fiber.sibling;
    if(sibling ! = =null) {
      nextEffect = sibling;
      return; } nextEffect = fiber.return; }}function commitPassiveUnmountOnFiber(finishedWork: Fiber) :void {
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent: {
        // Execute 'EffectFlag' as HookPassivecommitHookEffectListUnmount( HookPassive | HookHasEffect, finishedWork, finishedWork.return, ); }}}Copy the code

BeforeMutation phase processing

BeforeMutation is the first stage of the COMMIT process, which is performed before the DOM rendering (Mutation stage) in the browse area. The getSnapshotBeforeUpdate life cycle is processed in this phase.

The starting point of beforeMutation function for commitBeforeMutatuonEffects

const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
  root,
  finishedWork,
);
Copy the code

Alternately with depth traversal commitBeforeMutatuonEffects will from Root Fiber perform the begin and complete function.

Begin: There is no special processing logic.

Complete phase: to perform commitBeforeMutationEffectsOnFiber logic. The deep traversal logic here is very similar to the beginWork/completeWork logic in the Render stage.

export function commitBeforeMutationEffects(root: FiberRoot, firstChild: Fiber,) {... nextEffect = firstChild; commitBeforeMutationEffects_begin(); . }function commitBeforeMutationEffects_begin() {
  while(nextEffect ! = =null) {
    const fiber = nextEffect;
    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;
    commitBeforeMutationEffectsOnFiber(fiber);
    const sibling = fiber.sibling;
    if(sibling ! = =null) {
      nextEffect = sibling;
      return; } nextEffect = fiber.return; }}Copy the code

CommitBeforeMutationEffectsOnFiber logic is simpler, determine whether the current Fiber to exist, the Snapshot of the Flag. If so, the corresponding getSnapshotBeforeUpdate function is executed.

function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
  const current = finishedWork.alternate;
  const flags = finishedWork.flags;
  if((flags & Snapshot) ! == NoFlags) {switch (finishedWork.tag) {
      case FunctionComponent:
      case ForwardRef:
      case SimpleMemoComponent: 
      case HostComponent:
      case HostText:
      case HostPortal:
      case IncompleteClassComponent:{
      // Nothing to do for these component types
        break;
      }
      case ClassComponent: {
        if(current ! = =null) {
          const prevProps = current.memoizedProps;
          const prevState = current.memoizedState;
          const instance = finishedWork.stateNode;
          // We could update instance props and state here,
          // but instead we rely on them being set during last render.
          // TODO: revisit this when we implement resuming.
          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;
      }
      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.',); }}}}Copy the code

Processing of Mutation stage

BeforeMutation processing is complete, the logic of the Mutation phase is executed. At this stage React will render Fiber from WorkInProgress on the browser.

Like beforeMutation, the starting point of Mutatio is commitMutationEffects

const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
  root,
  finishedWork,
);
// The next phase is the mutation phase, where we mutate the host tree.
commitMutationEffects(root, finishedWork, lanes);
Copy the code

CommitMutationEffects will go through the BEGIN and complete phases in depth.

export function commitMutationEffects(root: FiberRoot, firstChild: Fiber, committedLanes: Lanes,) {
  inProgressLanes = committedLanes;
  inProgressRoot = root;
  nextEffect = firstChild;
  commitMutationEffects_begin(root);
  inProgressLanes = null;
  inProgressRoot = null;
}

function commitMutationEffects_begin(root: FiberRoot) {
  while(nextEffect ! = =null) {
    const fiber = nextEffect;
    // TODO: Should wrap this in flags check, too, as optimization
    const deletions = fiber.deletions;
    if(deletions ! = =null) {
      for (let i = 0; i < deletions.length; i++) {
        constchildToDelete = deletions[i]; commitDeletion(root, childToDelete, fiber); }}const child = fiber.child;
    if((fiber.subtreeFlags & MutationMask) ! == NoFlags && child ! = =null) {
      nextEffect = child;
    } else{ commitMutationEffects_complete(root); }}}function commitMutationEffects_complete(root: FiberRoot) {
  while(nextEffect ! = =null) {
    const fiber = nextEffect;
    commitMutationEffectsOnFiber(fiber, root);
    const sibling = fiber.sibling;
    if(sibling ! = =null) {
      nextEffect = sibling;
      return; } nextEffect = fiber.return; }}Copy the code

Begin phase: In the Render phase, the Child_Fiber portion is labeled as DELETE, and the corresponding DOM node is deleted at this point. If the Child_Fiber node of the current Fiber does not have flags such as Placement, Update, ChildDeletion, and ContentReset, execute the complete phase.

Complete phase: complete stage will perform commitMutationEffectsOnFiber Fiber processing. According to the different processing of flag for commitMutationEffectsOnFiber stage. Placement, for example, renders Fiber’s corresponding DOM in the browser. Update updates the corresponding DOM node.

function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) {
  const flags = finishedWork.flags;
  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;
    }
    case Update: {
      const current = finishedWork.alternate;
      commitWork(current, finishedWork);
      break; }}}Copy the code

The commitWork logic above executes the destory function of useLayoutEffect (the return value of useLayoutEffect). The logic is as follows, performs commitHookEffectListUnmount processing all EffectTag HookLayout | HookHasEffect Fiber, perform his destory function.

There appeared a new HookInsertion | HookHasEffect logo, according to a source of speculation is likely to be a new hooks useInsertionEffect

function commitWork(current: Fiber | null, finishedWork: Fiber) :void {
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case MemoComponent:
    case SimpleMemoComponent: {
      commitHookEffectListUnmount(
        HookInsertion | HookHasEffect,
        finishedWork,
        finishedWork.return,
      );
      commitHookEffectListMount(HookInsertion | HookHasEffect, finishedWork);
      commitHookEffectListUnmount(
        HookLayout | HookHasEffect,
        finishedWork,
        finishedWork.return,
      );
      return;
    }
      
    case ClassComponent: {
      return;
    }
      
    case HostComponent: {
      const instance: Instance = finishedWork.stateNode;
      if(instance ! =null) {
        // Commit the work prepared earlier.
        const newProps = finishedWork.memoizedProps;
        // For hydration we reuse the update path but we treat the oldProps
        // as the newProps. The updatePayload will contain the real change in
        // this case.
        constoldProps = current ! = =null ? current.memoizedProps : newProps;
        const type = finishedWork.type;
        // TODO: Type the updateQueue to be specific to host components.
        const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
        finishedWork.updateQueue = null;
        if(updatePayload ! = =null) {
          commitUpdate(
            instance,
            updatePayload,
            type, oldProps, newProps, finishedWork, ); }}return;
    }

    case HostText: {
      const textInstance: TextInstance = finishedWork.stateNode;
      const newText: string = finishedWork.memoizedProps;
      // For hydration we reuse the update path but we treat the oldProps
      // as the newProps. The updatePayload will contain the real change in
      // this case.
      const oldText: string= current ! = =null ? current.memoizedProps : newText;
      commitTextUpdate(textInstance, oldText, newText);
      return; }}}function commitHookEffectListUnmount(
  flags: HookFlags,
  finishedWork: Fiber,
  nearestMountedAncestor: Fiber | null.) {
 	const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
  constlastEffect = updateQueue ! = =null ? updateQueue.lastEffect : null;
  if(lastEffect ! = =null) {
    const firstEffect = lastEffect.next;
    let effect = firstEffect;
    do {
      if ((effect.tag & flags) === flags) {
        // Unmount
        const destroy = effect.destroy;
        effect.destroy = undefined;
        if(destroy ! = =undefined) {
          safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
        }
      }
      effect = effect.next;
    } while (effect !== firstEffect);
  }
}
Copy the code

Processing of Layout stage

The Layout stage occurs after the browser has rendered, and the main functions include the execution of the Create function for componentDidMount and useLayoutEffect. Also mount the useEffect create/destory asynchronous function.

The layout phase logic starts with commitLayoutEffects and points root’s current to finishedWork(workInProgress) before processing the layout phase logic.

// The work-in-progress tree is now the current tree. This must come after
// the mutation phase, so that the previous tree is still current during
// componentWillUnmount, but before the layout phase, so that the finished
// work is current during componentDidMount/Update.
root.current = finishedWork;
commitLayoutEffects(finishedWork, root, lanes);
Copy the code

Similarly, commitLayoutEffects have two stages, begin and complete.

Begin: There is no special logic

Complete phase: perform commitLayoutEffectOnFiber logic

As an additional mention of OffscreenComponent, this is a new API that will be used to implement keep-alive functionality similar to Vue.

export function commitLayoutEffects(finishedWork: Fiber, root: FiberRoot, committedLanes: Lanes,) :void {
  commitLayoutEffects_begin(finishedWork, root, committedLanes);
}

function commitLayoutEffects_begin(subtreeRoot: Fiber, root: FiberRoot, committedLanes: Lanes,) {
  // Suspense layout effects semantics don't change for legacy roots.
  constisModernRoot = (subtreeRoot.mode & ConcurrentMode) ! == NoMode;while(nextEffect ! = =null) {
    const fiber = nextEffect;
    const firstChild = fiber.child;

    if (
      enableSuspenseLayoutEffectSemantics &&
      fiber.tag === OffscreenComponent &&
      isModernRoot
    ) {
        ...
      }
        
    if((fiber.subtreeFlags & LayoutMask) ! == NoFlags && firstChild ! = =null) {
      ensureCorrectReturnPointer(firstChild, fiber);
      nextEffect = firstChild;
    } else{ commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes); }}}function commitLayoutMountEffects_complete(subtreeRoot: Fiber, root: FiberRoot, committedLanes: Lanes,) {
  while(nextEffect ! = =null) {
    const fiber = nextEffect;
    if((fiber.flags & LayoutMask) ! == NoFlags) {const current = fiber.alternate;
      commitLayoutEffectOnFiber(root, current, fiber, committedLanes);
    if (fiber === subtreeRoot) {
      nextEffect = null;
      return;
    }
    const sibling = fiber.sibling;
    if(sibling ! = =null) {
      nextEffect = sibling;
      return; } nextEffect = fiber.return; }}Copy the code

CommitLayoutEffectOnFiber performed in Class Component componentDidMount/componentDidUpdate Function and the Function of Component UseLayoutEffect create function in. The following code

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: {
        // Execute the useLayoutEffect of the function component
        commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
        break;
      }
      case ClassComponent: 
        const instance = finishedWork.stateNode;
        if (finishedWork.flags & Update) {
          if(! offscreenSubtreeWasHidden) {if (current === null) {
              // Implement componentDidMount
              instance.componentDidMount();
            } else {
              const prevProps =
                finishedWork.elementType === finishedWork.type
                  ? current.memoizedProps
                  : resolveDefaultProps(
                      finishedWork.type,
                      current.memoizedProps,
                    );
              const prevState = current.memoizedState;
              // We could update instance props and state here,
              // but instead we rely on them being set during last render.
              // TODO: revisit this when we implement resuming.
                instance.componentDidUpdate(
                  prevProps,
                  prevState,
                  instance.__reactInternalSnapshotBeforeUpdate,
                )
          }
        }

        const updateQueue: UpdateQueue<
          *,
        > | null = (finishedWork.updateQueue: any);
          // Execute the second argument of setState
          commitUpdateQueue(finishedWork, updateQueue, instance);
        }
        break;
      }
      case HostRoot: {
        // Execute the second argument to render
        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(finishedWork, updateQueue, instance);
        }
        break; }}Copy the code

Review past

  1. React Principle Analysis (1
  2. React source code analysis (2) — Fiber render phase

Refer to the article

  1. Juejin. Cn/post / 691962…
  2. Juejin. Cn/post / 691779…
  3. react.iamkasong.com/