React 的源码与原理解读(七):commit 阶段

写在专栏开头(叠甲)

  1. 作者并不是前端技术专家,也只是一名喜欢学习新东西的前端技术小白,想要学习源码只是为了应付急转直下的前端行情和找工作的需要,这篇专栏是作者学习的过程中自己的思考和体会,也有很多参考其他教程的部分,如果存在错误或者问题,欢迎向作者指出,作者保证内容 100% 正确,请不要将本专栏作为参考答案。

  2. 本专栏的阅读需要你具有一定的 React 基础、 JavaScript 基础和前端工程化的基础,作者并不会讲解很多基础的知识点,例如:babel 是什么,jsx 的语法是什么,需要时请自行查阅相关资料。

  3. 本专栏很多部分参考了大量其他教程,若有雷同,那是作者抄袭他们的,所以本教程完全开源,你可以当成作者对各类教程进行了整合、总结并加入了自己的理解。

本一节的内容

本节的我们将谈谈 commit 中 React 的操作,因为在之前的教程中,我们已经讲到了 React 怎么样把 element 节点变成 Fiber 树,怎么样通过 DIFF 算法更新我们的 Fiber 树,那么现在我们需要将我们的 Fiber 树的更新同步到我们的真实 DOM 上展示给用户,这个阶段就是 Commit 阶段

从上个阶段说起

现在我们把我们的视线拉回到我们的第四篇教程 —— updateContainer 相关的部分,我们可以看到我们之前一笔带过的两个函数:performConcurrentWorkOnRoot performSyncWorkOnRoot ,我们现在来看看他们的结构:

  • 他们的逻辑大致相同,只不过是ConcurrentWork 需要额外处理一下并发任务相关的进程调度和优先级的问题
  • 通过教程 5 和 6 的学习我们知道了,在 renderRootSyncrenderRootConcurrent 中,我们生成了我们的 Fiber 树,所以在这两个函数之后,我们可以拿到我们的 Fiber 树了
  • 在经过一些列的错误处理函数后(生成我们的 Fiber 很有可能出现问题)我们最终到达了 commitRoot 这个函数,这个函数传入我们刚刚生成的 Fiber 树的架构 。其中 sync 的任务直接调用,而 Concurrent 的任务因为需要通过 finishConcurrentRender 这个函数,根据状态判定是不是完成了我们的渲染,然后调用 commitRootWhenReady (其中调用了 commitRoot)进入我们的 commit 阶段(关于这两种任务的区别我们会后续单独来讲)
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

那么可见 commitRoot 这个函数就是我们在生成了我们的 Fiber 之后函数的统一入口,它又调用了 commitRootImpl 这个函数,我们现在来看看这个函数做了什么。首先需要明确,在 rootFiber.firstEffect 上保存了一条需要执行副作用的Fiber节点的单向链表 effectList ,这些 Fiber 节点的updateQueue 中保存了变化的props:

  • 它首先执行完毕之前没处理完成的 useEffec,这个会在我们讲解 hook 部分的时候详细展开
  • 之后为我们的变量赋值,并且进行 FiberRoot 状态重置的工作
  • 之后它进行了三次遍历 effectList ,分别处理 BeforeMutationEffects,MutationEffects 和 LayoutEffects 三个阶段的处理:
    • BeforeMutationEffects 主要是更新class组件实例上的state、props 等,以及执行 getSnapshotBeforeUpdate 生命周期函数
    • MutationEffects 主要是完成副作用的执行,主要包括重置文本节点以及真实 dom 节点的插入、删除和更新等操作。在完成这些以后,我们就可以切换我们的 current 树了,此时我们的新的工作树已经建立完毕,老的树将作为下一次的缓冲树使用(双缓冲结构)
    • LayoutEffects 主要是去触发 componentDidMount、componentDidUpdate 以及各种回调函数等
  • 最后,它还要处理 useEffect 相关的内容,因为在 commit 阶段会触发一些生命周期钩子,所以还要执行相关的同步任务
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);
    // 新的树生成完毕了,改变 current 的指向
    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

我们依次来看每个阶段,在BeforeMutationEffects 这个阶段,我们的来看看它做了什么:

  • 这阶段会调用 commitBeforeMutationEffects函数处理

  • commitBeforeMutationEffects 函数会依次调用 commitBeforeMutationEffects_begincommitBeforeMutationEffects_completecommitBeforeMutationEffectsOnFibergetSnapShotBeforeUpdate函数,也就是触发了 getSnapShotBeforeUpdate 这个生命周期

  • commitBeforeMutationEffects_begin 函数里,获取了当前阶段阶段需要删除的孩子(在上一个阶段的 diff 中生成),然后对他们执行相关的操作,这里主要是处理DOM节点渲染/删除后的 focusblur 逻辑。之后深度遍历直到没有孩子为止

  • commitBeforeMutationEffects_complete 中当我们遍历到的节点没有孩子,我们就遍历它的兄弟,也就是说 commitBeforeMutationEffects_begin 函数和 commitBeforeMutationEffects_complete 函数一起对我们的Fiber 进行了遍历(遍历的方式和之前的遍历 element 的时候大体一致)

  • commitBeforeMutationEffectsOnFiber 则是用于处理每一个 Fiber,主要是对 ClassComponent 组件进行处理,更新实例上的state、props 等,以及执行 getSnapshotBeforeUpdate 生命周期函数:

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

前一个阶段只是做了一些准备工作,而在MutationEffects 这个阶段,我们就需要将我们的修改同步到我们的 DOM 上了,我们来看看它做了什么:

  • 对不同类型的 fiber 在MutationEffects 这个阶段会进行不同的处理,但有一些公共逻辑会执行的,那就是删除操作和插入操作
  • 首先是调用 recursivelyTraverseMutationEffects 方法,这个方法会执行删除逻辑,完成删除逻辑后,
  • 接着就是调用 commitReconciliationEffects,这个方法负责往真实 DOM 树中插入 DOM 节点
  • 最后我们根据不同的组件再来做不同的操作
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;
    }
  }
}

我们先来看 recursivelyTraverseMutationEffects 的操作,它获取了之前我们上一个阶段存放在 deletions 中的孩子,然后依次调用了 commitDeletionEffects 方法,这个方法又会调用 commitDeletionEffectsOnFiber 这个函数,这个函数需要分类讨论:

  • 对于原生组件,我们这样做:
    • 先将它 ref 指向的元素设置为 null
    • 向下遍历子节点,执行删除逻辑
    • 最后调用 removeChild 方法删除真实 DOM
  • 对于类组件
    • 先将它 ref 指向的元素设置为 null
    • 之后调用它的 componentWillUnmount 生命周期函数
    • 向下遍历子节点,执行删除逻辑,因为它不对应真实 DOM 所以不需要删除,同时,它的孩子节点都是其他组件或者真实原生标签,所以只需要递归删除孩子节点即可
  • 对于函数组件
    • 我们需要遍历它的 updateQueue 执行 useInsertionEffect / useLayoutEffect 的回调函数所返回的函数(处理副作用)
    • 向下遍历子节点,执行删除逻辑
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;
    }
  }
}

之后我们来看看另一个插入的逻辑 commitReconciliationEffects,它只会在原生组件和 fiber 根节点上操作,并不操作函数组件和类组件,它的路逻辑是:

  • 判断节点的父 Fiber 是不是需要重置,如果是则调用 parent.textContent = '' 来重置它的内容
  • 之后寻找它有没有兄弟,如果有则调用 insertBefore 方法将内容插入到兄弟节点之前,如果没有,则调用父节点的 appendChild 进行插入
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.',
      );
  }
}

现在我们回到我们的 commitMutationEffectsOnFiber ,我们来看看在完成了删除和 插入操作后三中不同的节点又做了什么操作:

  • 对于我们的类组件,我们不做处理
  • 对于我们的函数组件,我们处理 useInsertionEffect ,useLayoutEffect 的副作用
  • 对于原生组件,如果可以复用,我们调用 commitUpdate 执行更新逻辑,否则直接重置这个原生节点
  • 如果是原生文本,我们调用 commitTextUpdate 函数,更新其文本内容
  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;
    }
    //...
}

最后我们来看看这个更新函数,它的逻辑很简单:它获取了真实 dom 节点实例、props 以及 updateQueue ,将 props 的属性一一对应应用到真实 DOM 上

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

最后我们来看看 LayoutEffects 这个阶段,这个阶段我们已经可以调用到我们的真实 DOM 了,它主要是执行 componentDidMountcomponentDidUpdate 生命周期,我们来看看逻辑:

  • 它的遍历过程和 BeforeMutationEffects 基本一致。都是使用了深度优先遍历的方式,这里我们不再具体阐述了,我们直接来看commitLayoutEffectOnFiber 这个函数
  • 它还是分组件进行处理:
    • 如果是函数组件,我们执行它的 useLayoutEffect hooks 即可
    • 如果是类组件,我们需要执行它的 componentDidMount 或者 componentDidUpdate 生命周期,之后还要处理 commitUpdateQueue 中的回调函数
    • 如果根节点,我们需要处理其回调,也就是是 ReactDOM.render 的调回函数
    • 如果是原生组件,我们处理其自动更新的情况
  • commitUpdateQueue 函数会对 finishedQueue 上面的 effects 进行遍历,若有 callback,则执行 callback。同时会重置 finishedQueue 上面的 effects 为 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.',
        );
    }
  }
}

总结

上述就是我们的 commit 阶段的内容,我们来总结一下它运行流程,大致可以分为五个阶段来说

  • 在进入BeforeMutationEffects 函数之前,我们先处理 useEffect 相关的任务并且进行一些初始化的工作
  • 之后进入 BeforeMutationEffects 阶段,我们深度优先遍历我们的 Fiber 树,处理DOM节点渲染/删除后的 focusblur 逻辑,然后处理我们 clas 组件的 getSnapshotBeforeUpdate 生命周期
  • 之后我们进入 MutationEffects 阶段,这个阶段里,我们执行 recursivelyTraverseMutationEffects 方法对需要删除的节点进行处理,再执行 commitReconciliationEffects 逻辑插入DOM,之后分别进行处理
    • 对于我们的函数组件,我们处理 useInsertionEffect ,useLayoutEffect 的副作用
    • 对于原生组件和文本,如果调用 commitUpdate 执行更新逻辑(复用)
  • 最后我们进入 LayoutEffects 阶段,主要是执行类组件的 componentDidMountcomponentDidUpdate 生命周期或者函数组件的 useLayoutEffect hooks ,然后对 finishedQueue 上面的 effects 的回调进行处理
  • 三阶段结束后,再次处理 useEffect 相关的内容,再执行 commit 阶段相关的同步任务即可

经过上述的操作,React 的节点从我们构建的 Fiber 同步到了真实的 DOM 上展示给了用户,相关的生命周期和 Hooks 也得到了触发,至此,一个从 jsx 开始的 React 的渲染过程已经讲完了,我们之后会在讲完调度后再完整的来梳理一遍。

现在我们来看看还有什么问题没有解决:

  • 我们在之前的讲解过默认跳过了优先级和调度相关的内容,这其实是一个很重要的阶段,scheduler 阶段,之后我们会结合我们之前讲解渲染的过程来讲解我们的 scheduler 是怎么安排任务和调度进程的
  • 我们已经提到了部分的 hooks 的内容,但是并没有系统的讲到 hooks 是怎么起作用的,我们之后会单独开几篇来讲我们的 hooks 是怎么挂载和生效的

那么这两个问题我们会在后续的教程里给出解答

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

摸鱼老萌新

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值