React18代码的探索(六)

73 阅读6分钟

当完成渲染流程后,需要finishConcurrentRender提交渲染

1.finishConcurrentRender

该函数 finishConcurrentRender 是React并发渲染流程的最终处理阶段,主要处理六种渲染结果状态:根据并发渲染的结果状态,决定是立即提交、延迟提交还是处理错误

function finishConcurrentRender(root, exitStatus, lanes) {
 switch (exitStatus) {
   // 异常状态处理
   case RootInProgress:
   case RootFatalErrored:
     throw new Error('Root did not complete. This is a bug in React.');

   // 错误重试处理
   case RootErrored:
     commitRoot(...); // 提交包含错误信息的根节点
     break;

   // 挂起状态处理
   case RootSuspended:
     markRootSuspended(root, lanes); // 标记根节点为挂起状态
     if (仅包含重试操作) {
       const msUntilTimeout = ...; // 计算节流时间
       if (需要延迟提交) {
         root.timeoutHandle = scheduleTimeout(...); // 设置延迟提交
         break;
       }
     }
     commitRoot(...); // 立即提交
     break;

   // 延迟挂起处理
   case RootSuspendedWithDelay:
     if (仅过渡操作) {
       break; // 过渡操作不提交占位符
     }
     if (需要JND优化) {
       const msUntilTimeout = jnd(...); // 计算可感知差异时间
       root.timeoutHandle = scheduleTimeout(...); // 设置优化延迟
       break;
     }
     commitRoot(...); // 提交占位符
     break;

   // 完成状态处理
   case RootCompleted:
     commitRoot(...); // 正常提交完成
     break;
 }
}
graph TD
    A[RootInProgress] --> B[抛出异常]
    A --> C[RootErrored]
    A --> D[RootSuspended]
    A --> E[RootSuspendedWithDelay]
    A --> F[RootCompleted]
    C --> G[立即提交]
    D --> H{是否仅重试?}
    H -->|是| I[延迟提交]
    H -->|否| J[立即提交]
    E --> K{是否过渡?}
    K -->|是| L[跳过提交]
    K -->|否| M[优化延迟]
    F --> N[正常提交]

commitRoot 提交入口函数

commitRoot函数是React提交阶段的入口,负责将协调阶段产生的副作用应用到DOM上

该函数的参数:root是Fiber树的根节点,recoverableErrors是可恢复的错误数组,transitions是过渡相关的数组

graph TD
   A[开始] --> B[保存当前状态]
   B --> C[设置最高优先级]
   C --> D[执行实际提交]
   D --> E[恢复原始状态]
   E --> F[返回null]
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;
}

commitRootImpl

commitRootImpl是提交阶段的核心实现,负责处理副作用、调用生命周期方法以及更新DOM

处理被动效果(passive effects),使用do-while循环调用flushPassiveEffects,直到所有被动效果处理完毕

检查执行上下文,确保当前不在渲染或提交阶段,避免重复操作

如果finishedWork为null,说明没有需要提交的内容,函数直接返回。否则,继续处理

处理回调节点和优先级,清理root的回调相关状态,确保新的回调可以被调度。然后是合并剩余的车道(lanes),考虑并发更新,标记根节点为完成状态

处理被动效果的调度,如果有被动效果存在,安排回调函数在之后处理

检查整个树是否有副作用,分为子树和根节点的效果。如果有,进入提交阶段的不同子阶段:before mutation、mutation、layout

处理完所有效果后,恢复执行上下文和优先级,确保后续操作不受影响。如果没有效果,直接更新root.current并记录时间

最后是处理被动效果的引用,安排后续的清理和调度。检查剩余的工作车道,处理可能的同步更新循环,刷新同步回调,并结束提交阶段的标记

协调阶段完成 → commitRootImpl → 提交到DOM
                   ↓
 完成DOM更新/生命周期调用/effects调度
graph TD
   A[开始] --> B[清理被动效果]
   B --> C[环境检查]
   C --> D{是否可提交?}
   D -->|否| E[直接返回]
   D -->|是| F[提交前准备]
   F --> G[Before Mutation]
   G --> H[Mutation]
   H --> I[Layout]
   I --> J[被动效果调度]
   J --> K[收尾工作]
function commitRootImpl(...) {
 // 阶段1:清理被动效果
 do {
   flushPassiveEffects(); 
 } while (rootWithPendingPassiveEffects !== null); // 循环直到所有被动effects处理完成

 // 阶段2:准备提交环境
 if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
   throw new Error('Should not already be working.'); // 确保不在渲染/提交阶段
 }

 const finishedWork = root.finishedWork; // 获取已完成的工作树
 const lanes = root.finishedLanes; // 关联的优先级通道

 // 开发模式调试标记
 if (__DEV__ && enableDebugTracing) logCommitStarted(lanes);
 if (enableSchedulingProfiler) markCommitStarted(lanes);

 // 阶段3:提交前检查
 if (finishedWork === null) return null; // 无内容可提交
 root.finishedWork = null; // 重置完成状态
 root.finishedLanes = NoLanes;

 // 阶段4:提交核心逻辑
 if (subtreeHasEffects || rootHasEffect) { // 存在需要处理的副作用
   const prevTransition = ReactCurrentBatchConfig.transition;
   ReactCurrentBatchConfig.transition = null; // 临时禁用过渡

   // 子阶段1:Before Mutation(DOM变更前)
   const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(...);

   // 子阶段2:Mutation(DOM变更)
   commitMutationEffects(root, finishedWork, lanes);
   resetAfterCommit(root.containerInfo); // 重置宿主环境

   // 子阶段3:Layout(布局效果)
   root.current = finishedWork; // 切换当前树
   commitLayoutEffects(finishedWork, root, lanes);
   requestPaint(); // 请求浏览器重绘
 }

 // 阶段5:被动效果调度
 if (rootDoesHavePassiveEffects) { // 存在延迟的被动效果
   scheduleCallback(NormalSchedulerPriority, () => {
     flushPassiveEffects(); // 异步调度被动effects
     return null;
   });
 }

 // 阶段6:收尾工作
 ensureRootIsScheduled(root, now()); // 安排后续任务
 if (hasUncaughtError) throw firstUncaughtError; // 抛出未捕获错误
 flushSyncCallbacks(); // 处理同步回调
}

commitBeforeMutationEffects

该函数主要是处理变更之前的副作用,比如调用getSnapshotBeforeUpdate生命周期方法

函数接收root和firstChild作为参数。root是Fiber树的根节点,firstChild是第一个需要处理的子节点

调用commitBeforeMutationEffects_begin,这个函数会遍历所有子节点,处理带有BeforeMutationMask标志的Fiber节点

提交阶段 → Before Mutation → commitBeforeMutationEffects
                     ↓
  处理DOM变更前的副作用(如getSnapshotBeforeUpdate)
export function commitBeforeMutationEffects(
 root: FiberRoot,
 firstChild: Fiber,
) {
 // 设置当前焦点实例句柄(用于DOM变更后恢复焦点)
 focusedInstanceHandle = prepareForCommit(root.containerInfo);

 // 初始化副作用处理指针
 nextEffect = firstChild;
 // 开始遍历处理BeforeMutation阶段的副作用
 commitBeforeMutationEffects_begin();

 // 保存是否需要触发blur事件的标志
 const shouldFire = shouldFireAfterActiveInstanceBlur;
 // 重置全局状态
 shouldFireAfterActiveInstanceBlur = false;
 focusedInstanceHandle = null;

 return shouldFire; // 返回是否需要触发blur事件
}

commitBeforeMutationEffects_begin

该函数的作用可能是遍历Fiber树,处理所有在DOM变更前需要完成的副作用

graph TD
   A[开始] --> B{nextEffect存在?}
   B -->|是| C[处理当前Fiber]
   C --> D{存在删除操作?}
   D -->|是| E[处理删除副作用]
   D -->|否| F[处理子节点]
   F --> G{子节点需处理?}
   G -->|是| H[深度遍历子节点]
   G -->|否| I[完成当前节点]
   H & I --> B
   B -->|否| J[结束]
  • nextEffect : 全局遍历指针,指向当前处理的Fiber节点
  • subtreeFlags : 记录子树副作用的位字段
function commitBeforeMutationEffects_begin() {
 // 开始遍历所有待处理的Fiber节点
 while (nextEffect !== null) {
   const fiber = nextEffect;  // 当前处理的Fiber节点

   // 仅当启用事件处理API时执行删除操作
   if (enableCreateEventHandleAPI) {
     // 处理当前Fiber的删除列表
     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(); // 完成当前节点的处理
   }
 }
}

commitMutationEffects

实际调用commitMutationEffectsOnFiber函数

根据fiber里的tag,分别调用commitReconciliationEffects

继而调用commitPlacement

function commitPlacement(finishedWork) {
 // ... 处理 DOM 插入/移动逻辑 ...
 const parent = getHostParent(finishedWork);
 insertOrAppendPlacementNode(finishedWork, parent);
}

commitLayoutEffects

调用commitLayoutEffects_begin函数,该函数在 React 的提交阶段被调用,主要负责协调和处理组件的生命周期钩子(如 componentDidMount、componentDidUpdate)以及 DOM 更新等操作,确保了浏览器完成绘制后同步执行布局相关的副作用操作

commitRoot → commitLayoutEffects → commitLayoutEffects_begin

function commitLayoutEffects_begin(
 subtreeRoot: Fiber,
 root: FiberRoot,
 committedLanes: Lanes
) {
 // 遍历逻辑实现...
 while (nextEffect !== null) {
   // 处理每个节点的 layout effects
   commitLayoutMountEffects_complete()
 }
}

commitLayoutMountEffects_complete

commitLayoutMountEffects_complete 函数是提交阶段布局子阶段(layout phase)的核心函数之一,主要负责执行DOM插入后的生命周期方法和布局相关副作用

function commitLayoutMountEffects_complete(
 root: FiberRoot,
 parent: Fiber | null,
 finishedWork: Fiber
) {
 // ... existing code ...
 
 // 遍历子树执行布局效果
 while (current !== null) {
   if ((current.flags & LayoutMask) !== NoFlags) {
      commitLayoutEffectOnFiber(root, current, fiber, committedLanes);
   }
   
   // 处理子节点和兄弟节点
   if (current.child !== null) {
     current.child.return = current;
     current = current.child;
     continue;
   }
   
   // ... 完成当前节点后的回溯逻辑 ...
 }
 // ... existing code ...
}
commitLayoutEffects()
├─ commitLayoutEffects_begin() // 初始遍历
└─ commitLayoutMountEffects_complete() // 完整子树处理

commitLayoutEffectOnFiber

commitLayoutEffectOnFiber 函数位于 React 协调器的提交阶段(commit phase),负责处理与布局相关的副作用(layout effects)

function commitLayoutEffectOnFiber(
 finishedRoot: FiberRoot,
 current: Fiber | null,
 finishedWork: Fiber,
 committedLanes: Lanes,
): void {
 switch (finishedWork.tag) {
   case HostRoot: {
     // 处理根节点布局效果
     if (supportsMutation) {
       if (finishedWork.flags & Update) {
         const root = finishedWork.stateNode;
         commitUpdateQueue(finishedWork, root, null);
       }
     }
     break;
   }
   case ClassComponent: {
     // 处理类组件生命周期
     const instance = finishedWork.stateNode;
     if (finishedWork.flags & Update) {
       if (current === null) {
         instance.componentDidMount(); // 挂载后
       } else {
         instance.componentDidUpdate(); // 更新后
       }
     }
     break;
   }
   case FunctionComponent: {
     // 处理函数组件布局effect
     if (finishedWork.flags & Update) {
       commitHookEffectListMount(
         HookLayout | HookHasEffect,
         finishedWork,
       );
     }
     break;
   }
   case HostComponent: {
     // 处理原生DOM组件
     const instance = finishedWork.stateNode;
     if (finishedWork.flags & Ref) {
       commitAttachRef(finishedWork); // 处理ref绑定
     }
     break;
   }
 }
}

处理完提交阶段后的副作用后,requestPaint申请浏览器重绘,设置needsPaint = true