React源码分析5-commit

本文详细分析了React应用在commit阶段的执行流程,包括commitBeforeMutationEffects、commitMutationEffects和commitLayoutEffects。主要内容涉及更新class组件状态、插入、更新和删除DOM节点、触发生命周期函数及回调处理。通过对React源码的解读,揭示了React如何高效地更新真实DOM。
摘要由CSDN通过智能技术生成

前两章讲到了,react 在 render 阶段的 completeUnitWork 执行完毕后,就执行 commitRoot 进入到了 commit 阶段,本章将讲解 commit 阶段执行过程源码。

总览

commit 阶段相比于 render 阶段要简单很多,因为大部分更新的前期操作都在 render 阶段做好了,commit 阶段主要做的是根据之前生成的 effectList,对相应的真实 dom 进行更新和渲染,这个阶段是不可中断的。

commit 阶段大致可以分为以下几个过程:

  1. 获取 effectList 链表,如果 root 上有 effect,则将其也添加进 effectList 中
  2. 对 effectList 进行第一次遍历,执行 commitBeforeMutationEffects 函数来更新class组件实例上的state、props 等,以及执行 getSnapshotBeforeUpdate 生命周期函数
  3. 对 effectList 进行第二次遍历,执行 commitMutationEffects 函数来完成副作用的执行,主要包括重置文本节点以及真实 dom 节点的插入、删除和更新等操作。
  4. 对 effectList 进行第三次遍历,执行 commitLayoutEffects 函数,去触发 componentDidMount、componentDidUpdate 以及各种回调函数等
  5. 最后进行一点变量还原之类的收尾,就完成了 commit 阶段

我们从 commit 阶段的入口函数 commitRoot 开始看:

// packages/react-reconciler/src/ReactFiberWorkLoop.old.js

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

它调用了 commitRootImpl 函数,所要做的工作都在这个函数中:

// packages/react-reconciler/src/ReactFiberWorkLoop.old.js

function commitRootImpl(root, renderPriorityLevel) {
   
  // ...
  const finishedWork = root.finishedWork;
  const lanes = root.finishedLanes;
  // ...

  // 获取 effectList 链表
  let firstEffect;
  if (finishedWork.flags > PerformedWork) {
   
    // 如果 root 上有 effect,则将其添加进 effectList 链表中
    if (finishedWork.lastEffect !== null) {
   
      finishedWork.lastEffect.nextEffect = finishedWork;
      firstEffect = finishedWork.firstEffect;
    } else {
   
      firstEffect = finishedWork;
    }
  } else {
   
    // 如果 root 上没有 effect,直接使用 finishedWork.firstEffect 作用链表头节点
    firstEffect = finishedWork.firstEffect;
  }

  if (firstEffect !== null) {
   
    // ...

    // 第一次遍历,执行 commitBeforeMutationEffects
    nextEffect = firstEffect;
    do {
   
      if (__DEV__) {
   
        invokeGuardedCallback(null, commitBeforeMutationEffects, null);
        // ...
      } else {
   
        try {
   
          commitBeforeMutationEffects();
        } catch (error) {
   
          // ...
        }
      }
    } while (nextEffect !== null);

    // ...
    // 第二次遍历,执行 commitMutationEffects
    nextEffect = firstEffect;
    do {
   
      if (__DEV__) {
   
        invokeGuardedCallback(
          null,
          commitMutationEffects,
          null,
          root,
          renderPriorityLevel,
        );
        // ...
      } else {
   
        try {
   
          commitMutationEffects(root, renderPriorityLevel);
        } catch (error) {
   
          // ...
        }
      }
    } while (nextEffect !== null);



    // 第三次遍历,执行 commitLayoutEffects
    nextEffect = firstEffect;
    do {
   
      if (__DEV__) {
   
        invokeGuardedCallback(null, commitLayoutEffects, null, root, lanes);
        // ...
      } else {
   
        try {
   
          commitLayoutEffects(root, lanes);
        } catch (error) {
   
          // ...
        }
      }
    } while (nextEffect !== null);

    nextEffect = null;

    // ...
  } else {
   
    // 没有任何副作用
    root.current = finishedWork;
    if (enableProfilerTimer) {
   
      recordCommitTime();
    }
  }

  // ...
}

commitBeforeMutationEffects

commitBeforeMutationEffects 中,会从 firstEffect 开始,通过 nextEffect 不断对 effectList 链表进行遍历,若是当前的 fiber 节点有 flags 副作用,则执行 commitBeforeMutationEffectOnFiber 节点去对针对 class 组件单独处理。

// packages/react-reconciler/src/ReactFiberWorkLoop.old.js

function commitBeforeMutationEffects() {
   
  while (nextEffect !== null) {
   
    // ...
    const flags = nextEffect.flags;
    if ((flags & Snapshot) !== NoFlags) {
   
      // 如果当前 fiber 节点有 flags 副作用
      commitBeforeMutationEffectOnFiber(current, nextEffect);
      // ...
    }
    // ...
    nextEffect = nextEffect.nextEffect;
  }
}

然后看一下 commitBeforeMutationEffectOnFiber,它里面根据 fiber 的 tag 属性,主要是对 ClassComponent 组件进行处理,更新 ClassComponent 实例上的state、props 等,以及执行 getSnapshotBeforeUpdate 生命周期函数:

// packages/react-reconciler/src/ReactFiberCommitWork.old.js

function commitBeforeMutationLifeCycles(
  current: Fiber | null,  finishedWork: Fiber,
): void {
   
  switch (finishedWork.tag) {
   
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent:
    case Block: {
   
      return;
    }
    case ClassComponent: {
   
      if (finishedWork.flags & Snapshot) {
   
        if (current !== null) {
   
          // 非首次加载的情况下
          // 获取上一次的 props 和 state
          const prevProps = current.memoizedProps;
          const prevState = current.memoizedState;
          // 获取当前 class 组件实例
          const instance = finishedWork.stateNode;
          // ...

          // 调用 getSnapshotBeforeUpdate 生命周期方法
          const snapshot = instance.getSnapshotBeforeUpdate(
            finishedWork.elementType === finishedWork.type
              ? prevProps
              : resolveDefaultProps(finishedWork.type, prevProps),
            prevState,
          );
          // ...
          // 将生成的 snapshot 保存到 instance.__reactInternalSnapshotBeforeUpdate 上,供 DidUpdate 生命周期使用
          instance.__reactInternalSnapshotBeforeUpdate = snapshot;
        }
      }
      return;
    }
    // ...
  }
}

相关参考视频讲解:进入学习

commitMutationEffects

commitMutationEffects 中会根据对 effectList 进行第二次遍历,根据 flags 的类型进行二进制与操作,然后根据结果去执行不同的操作,对真实 dom 进行修改:

  • ContentReset: 如果 flags 中包含 ContentReset 类型,代表文本节点内容改变,则执行 commitResetTextContent 重置文本节点的内容
  • Ref: 如果 flags 中包含 Ref 类型,则执行 commitDetachRef 更改 ref 对应的 current 的值
  • Placement: 上一章 diff 中讲过 Placement 代表插入,会执行 commitPlacement 去插入 dom 节点
  • Update: flags 包含 Update 则会执行 commitWork 执行更新操作
  • Deletion: flags 包含 Deletion 则会执行 commitDeletion 执行更新操作
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js

function commitMutationEffects(
  root: FiberRoot,  renderPriorityLevel: ReactPriorityLevel,
) {
   
  // 对 effectList 进行遍历
  while (nextEffect !== null) {
   
    setCurrentDebugFiberInDEV(nextEffect);

    const flags = nextEffect.flags;

    // ContentReset:重置文本节点
    if (flags & ContentReset) {
   
      commitResetTextContent(nextEffect);
    }

    // Ref:commitDetachRef 更新 ref 的 current 值
    if (flags & Ref) {
   
      const current = nextEffect.alternate;
      if (current !== null) {
   
        commitDetachRef(current)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值