React18代码的探索(四)

31 阅读10分钟

React18代码的探索(三)

接着上文,当我们确定了任务优先级后,调度并发任务scheduleCallback

scheduleCallback有两个入参,一个是schedulerPriorityLevel,一个是回调函数performConcurrentWorkOnRoot

1.performConcurrentWorkOnRoot

scheduleCallback的回调函数,并发模式的核心调度函数

负责在浏览器空闲时间中异步地执行根节点的更新工作

该函数会不断检查是否有空闲时间,如果有,则执行一个工作单元(work unit),否则会暂停执行,直到有空闲时间再继续‌

错误处理流程图

graph TD
    A[检测exitStatus] --> B{是否RootErrored?}
    B -->|是| C[获取错误重试车道]
    C --> D{有效车道?}
    D -->|是| E[同步重试渲染]
    B -->|否| F{是否RootFatalErrored?}
    F -->|是| G[准备新堆栈并抛出错误]
    B -->|否| H{是否RootDidNotComplete?}
    H -->|是| I[标记根为暂停状态]
    H -->|否| J[完成渲染提交准备]
   // 该函数是并发渲染的入口点,负责协调渲染阶段的打断与恢复
  // didTimeout 参数表示当前任务是否因时间分片用完而超时
  function performConcurrentWorkOnRoot(root, didTimeout) {
  // 判断是否需要中断渲染(通过 Scheduler 的 shouldYield 机制)
  if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
    throw new Error('Should not already be working.');
  }
  // 执行所有被调度的被动效果(useEffect回调)
  // 包含清理函数和执行新effect的逻辑
  // 返回布尔值表示是否有effects被执行
  flushPassiveEffects()
  // 获取当前渲染优先级车道
  let lanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  // 判断是否需要进行时间切片的条件
   const shouldTimeSlice =
   // 检查当前车道是否不包含阻塞性车道(需要同步执行的紧急任务)
    !includesBlockingLane(root, lanes) &&
    // 调度器超时配置检查(调试选项)或当前未超时
    !includesExpiredLane(root, lanes) &&
    (disableSchedulerTimeoutInWorkLoop || !didTimeout);
    
    // 根据条件选择渲染模式
    let exitStatus = shouldTimeSlice
      ? renderRootConcurrent(root, lanes)  // 可中断的并发模式
      : renderRootSync(root, lanes);       // 同步阻塞模式
  //React并发渲染错误恢复机制
  if (exitStatus === RootErrored) {
      //当发生可恢复错误时,通过 getLanesToRetrySynchronouslyOnError 获取需要同步重试的车道(lanes)
      const errorRetryLanes = getLanesToRetrySynchronouslyOnError(root);
      if (errorRetryLanes !== NoLanes) {
        lanes = errorRetryLanes;
        //调用 recoverFromConcurrentError 进行错误恢复,切换为同步渲染模式
        exitStatus = recoverFromConcurrentError(root, errorRetryLanes);
      }
    }
    //致命错误处理
    if (exitStatus === RootFatalErrored) {
      const fatalError = workInProgressRootFatalError;
      prepareFreshStack(root, NoLanes);  // 重置Fiber树
      markRootSuspended(root, lanes);   // 标记根节点为暂停状态
      ensureRootIsScheduled(root, now()); // 重新调度
      throw fatalError; // 抛出错误给上层边界
    }
    //外部状态一致性检查
    if (renderWasConcurrent &&
      !isRenderConsistentWithExternalStores(finishedWork)) {
      exitStatus = renderRootSync(root, lanes); // 强制同步渲染
    }
    //渲染完成处理
    root.finishedWork = finishedWork;
    root.finishedLanes = lanes;
    //调用 finishConcurrentRender 提交渲染结果
    finishConcurrentRender(root, exitStatus, lanes);

}

1.flushPassiveEffects处理被动效果

flushPassiveEffects是React 中处理被动效果(Passive Effects)的核心函数 ,主要功能是执行 useEffect 相关的副作用清理和触发操作

commitRoot(提交阶段)
→ scheduleCallback(调度被动效果) 
→ flushPassiveEffects(执行副作用)
 → flushPassiveEffectsImpl(实际处理)
   → commitPassiveUnmountEffects(清理旧 effect)
   → commitPassiveMountEffects(触发新 effect)
export function flushPassiveEffects(): boolean {
if (rootWithPendingPassiveEffects !== null) {
 // 缓存待处理 passive effects 的根节点和相关 lanes
 const root = rootWithPendingPassiveEffects;
 const remainingLanes = pendingPassiveEffectsRemainingLanes;
 pendingPassiveEffectsRemainingLanes = NoLanes;

 // 计算优先级
 const renderPriority = lanesToEventPriority(pendingPassiveEffectsLanes);
 const priority = lowerEventPriority(DefaultEventPriority, renderPriority);
 
 // 保存当前上下文
 const prevTransition = ReactCurrentBatchConfig.transition;
 const previousPriority = getCurrentUpdatePriority();

 try {
   // 设置新优先级并执行副作用
   ReactCurrentBatchConfig.transition = null;
   setCurrentUpdatePriority(priority);
   return flushPassiveEffectsImpl(); // 实际执行入口
 } finally {
   // 恢复上下文
   setCurrentUpdatePriority(previousPriority);
   ReactCurrentBatchConfig.transition = prevTransition;
   
   // 释放缓存池
   releaseRootPooledCache(root, remainingLanes);
 }
}
return false;
}

实际执行的是flushPassiveEffectsImpl函数

2.flushPassiveEffectsImpl 处理被动效果实现函数

这个函数是React处理被动副作用(如 useEffect )的核心机制,在浏览器空闲时段批量处理:

  1. 执行已卸载组件的清理函数
  2. 执行新effect的回调函数
  3. 收集性能分析数据
  4. 处理过渡追踪逻辑
function flushPassiveEffectsImpl() {
   //检查是否存在待处理的被动效果树 rootWithPendingPassiveEffects ,若不存在直接返回false
   if (rootWithPendingPassiveEffects === null) {
    return false;
  }
  
      // 获取当前需要处理的Fiber根节点和对应的优先级车道(lanes)
    // 重置相关状态变量
    const root = rootWithPendingPassiveEffects;
    const lanes = pendingPassiveEffectsLanes;
    rootWithPendingPassiveEffects = null;
    pendingPassiveEffectsLanes = NoLanes;
    
    //分为两个阶段:先执行所有卸载的清理函数,再执行新的effect回调
    commitPassiveUnmountEffects(root.current);  // 执行清理函数(useEffect返回的函数)
    commitPassiveMountEffects(root, root.current, lanes, transitions); // 执行effect回调
    
    //调度处理过渡效果的callback,使用空闲优先级
    if (enableTransitionTracing) {
      scheduleCallback(IdleSchedulerPriority, () =>
        processTransitionCallbacks(...)
      )
    }
}

commitPassiveUnmountEffects 被动卸载函数

实际调用的是commitPassiveUnmountEffects_begin函数

这是React被动卸载效果处理的入口函数,主要负责处理被删除Fiber树的useEffect清理函数。属于commit阶段passive effects处理流程的一部分

  • 遍历 nextEffect 链表处理被标记为ChildDeletion的fiber
  • 调用 commitPassiveUnmountEffectsInsideOfDeletedTree_begin 处理被删除树内部的effect清理
  • 根据 deletedTreeCleanUpLevel 配置执行不同级别的内存清理
  • 通过深度优先遍历处理所有被标记PassiveMask的子树
function commitPassiveUnmountEffects_begin() {
  while (nextEffect !== null) {
    const fiber = nextEffect;
    // 处理子节点删除的清理
    if ((fiber.flags & ChildDeletion) !== NoFlags) {
      // 遍历所有待删除的fiber
      deletions.forEach(fiberToDelete => {
        commitPassiveUnmountEffectsInsideOfDeletedTree_begin(fiberToDelete);
      });
      
      // 执行深度清理(断开alternate链接)
      if (deletedTreeCleanUpLevel >= 1) {
        // 断开被删除fiber与之前兄弟节点的链接
        previousFiber.child = null;
        while(detachedChild) {
          detachedChild.sibling = null;
          detachedChild = detachedChild.sibling;
        }
      }
    }
    
    // 递归处理子树
    if ((fiber.subtreeFlags & PassiveMask) && fiber.child) {
      nextEffect = fiber.child;
    } else {
      commitPassiveUnmountEffects_complete();
    }
  }
}

commitPassiveMountEffects

实际调用commitPassiveMountEffects_begin函数

该函数是React提交阶段处理被动挂载效果(Passive Effects)的入口函数,主要职责是遍历Fiber树并触发useEffect等副作用

  1. 遍历逻辑 通过 while (nextEffect !== null) 循环遍历Fiber链表,使用 nextEffect 指针进行深度优先遍历
  2. 子树检测 通过 (fiber.subtreeFlags & PassiveMask) !== NoFlags 检查当前Fiber子树是否包含需要处理的被动效果
  3. 遍历方向控制
  • 当子树存在被动效果时,将 nextEffect 指向第一个子节点继续深入遍历
  • 当子树没有被动效果时,调用 commitPassiveMountEffects_complete 切换到"complete"阶段处理兄弟节点或向上回溯
function commitPassiveMountEffects_begin(
  subtreeRoot: Fiber,
  root: FiberRoot,
  committedLanes: Lanes,
  committedTransitions: Array<Transition> | null,
) {
  while (nextEffect !== null) {
    const fiber = nextEffect;
    const firstChild = fiber.child;
    //检查当前Fiber子树是否包含需要处理的被动效果
    if ((fiber.subtreeFlags & PassiveMask) !== NoFlags && firstChild !== null) {
      firstChild.return = fiber;
      nextEffect = firstChild;
    } else {
      commitPassiveMountEffects_complete(
        subtreeRoot,
        root,
        committedLanes,
        committedTransitions,
      );
    }
  }
}

3.renderRootConcurrent 并发渲染函数

renderRootConcurrent负责协调整个Fiber树的异步渲染流程,实现了可中断的渲染机制

   function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
  // 保存执行上下文
  const prevExecutionContext = executionContext;
  executionContext |= RenderContext;
  
  // 初始化更新器跟踪
  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    if (enableUpdaterTracking) {
      // 开发工具相关的更新器状态维护
      restorePendingUpdaters(root, workInProgressRootRenderLanes);
      movePendingFibersToMemoized(root, lanes);
    }
    
    // 初始化过渡状态和渲染计时器
    workInProgressTransitions = getTransitionsForLanes(root, lanes);
    resetRenderTimer();
    prepareFreshStack(root, lanes); // 创建新的工作循环堆栈
  }

  // 开发模式调试跟踪
  if (__DEV__ && enableDebugTracing) {
    logRenderStarted(lanes);
  }

  // 启动并发工作循环
  do {
    try {
      workLoopConcurrent(); // 核心工作循环
      break;
    } catch (thrownValue) {
      handleError(root, thrownValue); // 错误边界处理
    }
  } while (true);

  // 清理阶段
  resetContextDependencies();
  popDispatcher(prevDispatcher);
  executionContext = prevExecutionContext;

  // 返回渲染结果状态
  return workInProgress !== null ? RootInProgress : workInProgressRootExitStatus;
}
并发渲染控制函数 workLoopConcurrent

该函数实现了并发模式下,React使用可中断的渲染机制,允许在浏览器空闲时执行任务,避免阻塞主线程

workInProgress是全局变量,表示当前正在处理的Fiber节点。

shouldYield()函数来自调度器(Scheduler),用于判断是否需要让出主线程,以便处理更高优先级的任务或用户交互。如果返回true,循环终止,渲染任务会被暂停,等待下次空闲时间继续。

function workLoopConcurrent() {
  // 执行工作直到调度器要求让出主线程
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}
让步检测函数 shouldYield 时间分片控制

该函数通常用于时间分片,确保长时间的任务不会阻塞主线程

时间分片控制

  • frameInterval (默认 5ms) 单帧时间阈值
  • continuousInputInterval (默认 50ms) 连续输入检测阈值
  • maxInterval (默认 300ms) 最大阻塞时间

采用三级检测机制:

  • 优先检测 needsPaint 标记的绘制请求
  • 短期阻塞仅检测离散输入(点击/按键)
  • 长期阻塞检测所有输入类型
  • 超长阻塞(>300ms)强制让出主线程
function shouldYieldToHost() {
  // 计算主线程已阻塞的时间
  const timeElapsed = getCurrentTime() - startTime;
  
  // 短期阻塞无需让出(小于单帧时间)
  if (timeElapsed < frameInterval) {
    return false;
  }

  // 长期阻塞处理逻辑
  if (enableIsInputPending) {
    // 优先处理绘制请求
    if (needsPaint) {
      return true; 
    }

    // 分级检测输入事件
    if (timeElapsed < continuousInputInterval) {
      return isInputPending?.(); // 仅检测离散输入
    } else if (timeElapsed < maxInterval) {
      return isInputPending?.(continuousOptions); // 检测所有输入类型
    } else {
      return true; // 超长阻塞强制让出
    }
  }

  // 后备策略:无输入检测能力时默认让出
  return true;
}
performUnitOfWork

该函数主要负责处理单个fiber

  1. Fiber节点处理入口
  • 接收 unitOfWork 参数作为当前要处理的Fiber节点
  • 通过 alternate 获取对应的current fiber(已提交的fiber树节点)
  1. 调试与性能分析
  • setCurrentDebugFiberInDEV 设置开发环境下的调试标记
  • 当启用性能分析时( enableProfilerTimer ),用 start/stopProfilerTimer 包裹beginWork执行过程
graph TD
    A[开始处理] --> B{是否启用性能分析?}
    B -->|是| C[开始计时]
    B -->|否| D[执行beginWork]
    C --> D
    D --> E[停止计时]
    E --> F[处理返回的next节点]
    D --> F
    F --> G{next存在?}
    G -->|是| H[设置workInProgress指针]
    G -->|否| I[完成当前节点]
 function performUnitOfWork(unitOfWork: Fiber): void {
  const current = unitOfWork.alternate;
  setCurrentDebugFiberInDEV(unitOfWork);

  let next;
  //根据是否启用性能分析计时器(`enableProfilerTimer`)和当前Fiber的模式(`ProfileMode`),决定是否在`beginWork`前后进行性能测量
  if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
  }

  resetCurrentDebugFiberInDEV();
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  if (next === null) {
  //当前节点没有子节点完成当前节点的工作
    completeUnitOfWork(unitOfWork);
  } else {
  //继续处理下一个节点
    workInProgress = next;
  }

  ReactCurrentOwner.current = null;
}
beginWork

beginWork函数是React协调过程的一部分,负责处理Fiber节点的更新。

它接收当前Fiber节点(current)、工作中的Fiber节点(workInProgress)以及渲染优先级(renderLanes)作为参数,返回下一个要处理的子Fiber节点或null

graph TD
  A[beginWork] --> B{current存在?}
  B -->|是| C[比较props/context]
  B -->|否| D[处理hydration]
  C --> E[设置didReceiveUpdate]
  E --> F[根据tag分发]
  D --> F
  F --> G[FunctionComponent]
  F --> H[ClassComponent]
  F --> I[HostRoot等20+类型]
渲染循环 → performUnitOfWork → beginWork → 组件特定更新方法
                        ↓
           生成子fiber树并返回下一个工作单元
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
// ... 开发环境处理 ...

// 核心逻辑
if (current !== null) {
  const oldProps = current.memoizedProps;
  const newProps = workInProgress.pendingProps;
  // 比较新旧props/context
  // 热更新
  if (oldProps !== newProps || hasLegacyContextChanged()) {
    didReceiveUpdate = true;
  } else {
    // 检查调度更新或context变化
     const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
      current,
      renderLanes,
    )

    //无更新且非错误边界重试
    if (
      !hasScheduledUpdateOrContext &&
      (workInProgress.flags & DidCapture) === NoFlags
    ) {
      didReceiveUpdate = false;
      //提前退出优化
      return attemptEarlyBailoutIfNoScheduledUpdate(
        current,
        workInProgress,
        renderLanes,
      );
    }
  }
} else {
  // 处理hydration场景的ID生成
}

// 清除当前fiber的优先级标记
workInProgress.lanes = NoLanes;

// 根据fiber类型分发处理
switch (workInProgress.tag) {
  case FunctionComponent: 
    return updateFunctionComponent(...);
  case ClassComponent:
    return updateClassComponent(...);
  // ... 其他20+组件类型处理 ...
}
}

以下是例举的类型整理

updateFunctionComponent

updateFunctionComponent是一个处理函数组件的函数,属于React协调过程的一部分。函数组件在React中是无状态的,但使用Hooks后可以有状态,所以需要处理Hooks相关的逻辑

Hooks处理核心函数renderWithHooks

function updateFunctionComponent(
 current,
 workInProgress,
 Component,
 nextProps: any,
 renderLanes,
) {
 // 开发环境校验逻辑
 if (__DEV__) {
   // 处理热重载后的组件类型变化
   if (workInProgress.type !== workInProgress.elementType) {
     // 校验更新后的组件propTypes
     const innerPropTypes = Component.propTypes;
     if (innerPropTypes) {
       checkPropTypes(/*...*/);
     }
   }
 }

 // 上下文处理
 let context;
 if (!disableLegacyContext) {
   // 获取未过滤的上下文
   const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
   // 获取过滤后的上下文
   context = getMaskedContext(workInProgress, unmaskedContext);
 }

 // 准备读取上下文
 prepareToReadContext(workInProgress, renderLanes);

 // 性能分析标记
 if (enableSchedulingProfiler) {
   markComponentRenderStarted(workInProgress);
 }

 // 核心渲染流程
 let nextChildren;
 if (__DEV__) {
   // 开发模式下的严格模式检查
   ReactCurrentOwner.current = workInProgress;
   setIsRendering(true);
   nextChildren = renderWithHooks(/*...*/);
   // 严格模式二次渲染检测副作用
   if (debugRenderPhaseSideEffectsForStrictMode) {
     // ...
   }
   setIsRendering(false);
 } else {
   // 生产环境直接渲染
   nextChildren = renderWithHooks(
     current,
     workInProgress,
     Component,
     nextProps,
     context,
     renderLanes,
   );
 }

 // 优化策略:跳过无更新的组件
 if (current !== null && !didReceiveUpdate) {
   bailoutHooks(current, workInProgress, renderLanes);
   return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
 }

 // 协调子节点
 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
 return workInProgress.child;
}
renderWithHooks Hooks处理核心

该函数是React Hooks实现的核心部分之一

renderWithHooks负责管理Hooks的生命周期,处理组件的挂载和更新,确保Hooks的顺序和状态正确,同时处理渲染过程中的更新和错误情况。这个函数在React的并发模式和Hooks机制中起着桥梁作用,连接了组件的渲染逻辑和Hooks的状态管理

export function renderWithHooks<Props, SecondArg>(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: (p: Props, arg: SecondArg) => any,
  props: Props,
  secondArg: SecondArg,
  nextRenderLanes: Lanes,
): any {

  renderLanes = nextRenderLanes;
  currentlyRenderingFiber = workInProgress;
    
    //重置 准备新的渲染过程
   workInProgress.memoizedState = null;
  workInProgress.updateQueue = null;
  workInProgress.lanes = NoLanes;
  
  //根据当前是挂载还是更新阶段,选择合适的Dispatcher,这在Hooks的mount和update阶段有不同的实现
  ReactCurrentDispatcher.current =
      current === null || current.memoizedState === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;
        
  //调用组件函数`Component`生成子元素      
 let children = Component(props, secondArg); 
 //检查是否存在渲染阶段的更新。如果存在,进入循环重新渲染,直到没有更多更新或达到限制次数
  if (didScheduleRenderPhaseUpdateDuringThisPass) {
    let numberOfReRenders: number = 0;
    do {
      didScheduleRenderPhaseUpdateDuringThisPass = false;
      localIdCounter = 0;

      if (numberOfReRenders >= RE_RENDER_LIMIT) {
        throw new Error(
          'Too many re-renders. React limits the number of renders to prevent ' +
            'an infinite loop.',
        );
      }

      numberOfReRenders += 1;

      // Start over from the beginning of the list
      currentHook = null;
      workInProgressHook = null;

      workInProgress.updateQueue = null;

      ReactCurrentDispatcher.current = __DEV__
        ? HooksDispatcherOnRerenderInDEV
        : HooksDispatcherOnRerender;

      children = Component(props, secondArg);
    } while (didScheduleRenderPhaseUpdateDuringThisPass);
  }
  
  //重置Dispatcher为`ContextOnlyDispatcher`
  ReactCurrentDispatcher.current = ContextOnlyDispatcher;
    
  //进行开发环境下的各种校验,比如Hook数量是否正确,静态标记是否一致等。最后处理上下文传播的延迟更新,并返回子元素
}

管理完hooks后,紧接这就到了核心的diff算法比较函数了

React18代码的探索(五)