调度系统和调和系统的桥梁

一、ensureRootIsScheduled

登记待调度根

ini 复制代码
export function ensureRootIsScheduled(root) {
  // 当前 root 就是链表尾 lastScheduledRoot 或 该 root 已经在链表中
  if (root === lastScheduledRoot || root.next !== null) {

  } else {
    if (lastScheduledRoot === null) {
      // 待调度根链表头节点
      firstScheduledRoot = lastScheduledRoot = root;
    } else {
      // 待调度根链表尾节点
      lastScheduledRoot.next = root;
      lastScheduledRoot = root;
    }
  }
  mightHavePendingSyncWork = true;

  ensureScheduleIsScheduled();

}

export function ensureScheduleIsScheduled() {
  if (!didScheduleMicrotask) { 
    // 是否已经注册了根批量调度微任务
    didScheduleMicrotask = true;
    scheduleImmediateRootScheduleTask();
  }
}

作用

  • 全局调度根链表维护 :维护一条全局单向链表 收集所有存在待更新的 FiberRoot,支持多根应用(多 ReactDOM.createRoot 实例)批量统一处理
    • 当前 root 就是链表尾 lastScheduledRoot 或该 root 已经在链表中,表示这个 root 已经登记过待调度,无需重复入队,避免重复链表操作
    • 否则把 root 加入待调度链表
  • 全局标记当前存在待同步 / 并发渲染任务 ,在 flushSync、批量同步刷新逻辑中会读取该标记
    • 若为 false,可直接快速退出同步刷新逻辑,不用遍历调度链表
    • 只要任意根新增更新,立刻置为 true,提示同步刷新逻辑需要遍历链表处理任务
  • 保证全局存在且仅存在一个处理根调度的微任务,用来批量处理链表内所有待调度根。当前没有排队的调度微任务时,才走创建逻辑
    • 同步设置锁,避免并发多次进入创建分支,保证微任务全局唯一
    • 创建唯一批量处理微任务,把所有根的调度计算延后到当前同步代码执行完毕后统一处理

设计意义

  • 支持多 root 应用 (如 portals 或微前端)
  • 合并同步多轮更新 :同步代码中连续多次 setState(循环、事件内多次更新),只会触发一次微任务,只遍历一次根链表,只创建一次 Scheduler 任务
  • 幂等防护 :多次调用 ensureScheduleIsScheduled 也只会触发一次微任务创建,是批量更新合并的关键锁

二、scheduleImmediateRootScheduleTask

负责创建批量处理根调度的异步任务,自动兼容浏览器微任务能力、规避渲染 / 提交上下文死循环,是连接同步更新登记与批量渲染调度的中间层

javascript 复制代码
function scheduleImmediateRootScheduleTask() {
  if (supportsMicrotasks) {
    scheduleMicrotask(() => {
      // 检查是否在 render/commit 中(在 Safari 中,iframe 的加载会强制刷新微任务队列)
      if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
        // 若在渲染中,用宏任务替代(防止无限循环)
        Scheduler_scheduleCallback(ImmediateSchedulerPriority, processRootScheduleInImmediateTask);
        return;
      }
      processRootScheduleInMicrotask();
    });
  } else {
    Scheduler_scheduleCallback(ImmediateSchedulerPriority, processRootScheduleInImmediateTask);
  }
}

function processRootScheduleInImmediateTask() {
  processRootScheduleInMicrotask();
}

作用

  • 浏览器支持 queueMicrotask:优先走微任务批量处理
    • 当前主线程是否正处在 render 调和阶段commit 提交阶段 :放弃微任务执行,改用 Scheduler 即时优先级宏任务延后批量调度至当前渲染栈完全结束
    • 否则直接执行微任务批量逻辑,同一同步周期所有更新合并一次处理,减少 Scheduler 碎片任务
  • 不支持微任务 / 当前处于渲染 / 提交上下文:降级为 Scheduler 即时优先级宏任务

设计意义

  • 优先微任务保证批量更新粒度 :微任务在当前同步调用栈清空后、浏览器渲染前执行,同一事件循环内所有同步 setState 会被完全合并,不会被页面渲染切割
  • 上下文安全防护 :区分 "空闲主线程" 和 "正在渲染 / 提交" 两种场景,动态切换微 / 宏任务,从底层规避并发渲染死循环,绝不允许批量调度逻辑打断正在进行的 render /commit 流程

三、processRootScheduleInMicrotask

批量消费全局待调度根链表

javascript 复制代码
function processRootScheduleInMicrotask() {
  didScheduleMicrotask = false;       // 重置去重标志
  // 默认清空 "存在待同步渲染任务" 标记,遍历所有根时再重新赋值
  mightHavePendingSyncWork = false;

  // 检查当前事件的 transition lane
  let syncTransitionLanes = NoLanes;
  // 本次用户交互事件(点击、跳转、popstate)产生的 useTransition 专属车道
  if (currentEventTransitionLane !== NoLane) {
    if (shouldAttemptEagerTransition()) {
      // popstate等场景强制同步渲染transition,保留滚动位置
      syncTransitionLanes = currentEventTransitionLane; 
    } else if (enableDefaultTransitionIndicator) {
      // 提前刷新Default车道,区分加载指示器逻辑
      syncTransitionLanes = DefaultLane;             
    }
  }

  let prev = null;
  let root = firstScheduledRoot;

  // 遍历所有 root,为每个 root 安排任务
  while (root !== null) {
    const next = root.next;
    // 为当前根创建Scheduler渲染任务,返回剩余待处理车道
    const nextLanes = scheduleTaskForRootDuringMicrotask(root, currentTime);
    // 分支1:当前根无剩余待更新车道,从链表移除
    if (nextLanes === NoLane) {
      // 该 root 无待处理工作 → 从调度链表中移除
      root.next = null;
      // 链表操作
      if (prev === null) {
        
        firstScheduledRoot = next;
      } else {
        prev.next = next;
      }
      if (next === null) {
        
        lastScheduledRoot = prev;
      }
      // 分支2:根仍有未处理更新,保留在链表中
    } else {
      prev = root;
      // 判断是否包含同步/手势过渡车道,更新mightHavePendingSyncWork
      if (syncTransitionLanes !== NoLanes || 
      includesSyncLane(nextLanes) ||
        (enableGestureTransition && isGestureRender(nextLanes))) {
        mightHavePendingSyncWork = true;
      }
    }
    root = next;
  }

  // 最后刷新同步工作
  // 检查是否有 ViewTransition 正在运行
  // 如果有,跳过同步刷新,等 ViewTransition 完成后再执行
  if (!hasPendingCommitEffects()) {
    flushSyncWorkAcrossRoots_impl(syncTransitionLanes, false);
  }
  if (currentEventTransitionLane !== NoLane) {
    currentEventTransitionLane = NoLane;
    startDefaultTransitionIndicatorIfNeeded();
  }
}

作用

  • 释放全局微任务锁:解锁微任务锁,允许下一轮更新重新创建调度微任务
  • 初始化全局同步任务标记:清空 "存在待同步渲染任务" 标记,遍历所有根时再重新赋值
  • 处理事件触发的 Transition 同步渲染车道
    • 页面前进后退需要同步渲染 Transition,保证滚动位置不丢失,把过渡车道标记为同步执行
    • 开启默认过渡指示器:提前刷新 Default 优先级任务,区分普通 setState 和过渡加载状态,避免指示器逻辑错乱
  • 遍历 firstScheduledRoot 单向链表,逐个为根创建并发渲染任务
    • 根无剩余工作,移除链表
      • 清空 root.next,标记该根脱离调度链表,清理无待更新的根、维护链表首尾指针
      • 分头部 / 中间 / 尾部节点更新 firstScheduledRootlastScheduledRoot 全局指针
    • 根仍有待更新,保留链表
      • 更新 prev 指针,维持链表遍历
      • 判断当前根剩余车道是否包含:同步 Transition 车道、同步车道、手势过渡渲染车道
      • 满足任一条件则置 mightHavePendingSyncWork = true,标记全局存在同步渲染任务
  • 判断是否存在未执行的被动副作用(useEffect)、提交挂起任务
    • 若存在待提交副作用,跳过本次同步刷新,等待副作用执行完毕再处理同步渲染,防止时序冲突
    • 批量执行同步渲染任务(同步 TransitionSyncLane 手势过渡),批量遍历所有根,同步执行 syncTransitionLanes 标记的过渡任务、全局 SyncLane 同步更新,直接同步渲染 DOM,不进入 Scheduler 异步分片
  • 重置事件过渡全局变量、启动默认过渡加载指示器
    • 清空本次交互过渡车道,下一次交互分配全新过渡车道
    • 根据本次过渡渲染结果,判断是否展示页面加载过渡指示器

设计意义

  • 锁生命周期闭环:微任务执行开头释放锁,下一次更新可正常走调度流程
  • 单一微任务批量消费,极致合并更新 :同一事件循环内所有同步 setState、多根更新全部收拢到一次微任务遍历,一次性批量创建所有渲染任务,杜绝碎片化 Scheduler 任务,是 React 批量更新的底层核心
  • 同步渲染后置执行:放在链表遍历完成后,避免同步渲染打断链表遍历逻辑
  • 副作用时序隔离 :存在 pending 副作用时暂缓同步渲染,保证 commiteffect、渲染顺序规范

四、scheduleTaskForRootDuringMicrotask

不做同步渲染,只负责向 Scheduler 注册 / 复用 / 取消渲染任务

javascript 复制代码
function scheduleTaskForRootDuringMicrotask(root, currentTime) {
  // 1. 检查是否有 lane 饿死 → 标记过期
  markStarvedLanesAsExpired(root, currentTime);

  // 2. 获取下一个要处理的 lanes
  
  // 存在待执行 useEffect 的根
  const rootWithPendingPassiveEffects = getRootWithPendingPassiveEffects();
  // 触发被动副作用对应的车道
  const pendingPassiveEffectsLanes = getPendingPassiveEffectsLanes();
  // 当前正在调和的根及其渲染车道
  const workInProgressRoot = getWorkInProgressRoot();
  // 当前正在调和的根的渲染车道
  const workInProgressRootRenderLanes = getWorkInProgressRootRenderLanes();
  // 根存在挂起提交(视图过渡、Suspense 提交中断)
  const rootHasPendingCommit =
    root.cancelPendingCommit !== null || root.timeoutHandle !== noTimeout;
  const nextLanes =
    enableYieldingBeforePassive && root === rootWithPendingPassiveEffects
      ? pendingPassiveEffectsLanes
      : getNextLanes(
          root,
          root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
          rootHasPendingCommit,
        );
  // 获取当前根已存在的调度任务,用于后续对比优先级、取消旧任务      
  const existingCallbackNode = root.callbackNode;
  if (nextLanes === NoLanes || 
      (root === workInProgressRoot && isWorkLoopSuspendedOnData()) ||
      root.cancelPendingCommit !== null
  ) {
    // 无事可做 → 取消已存在的 callback
    if (existingCallbackNode !== null) {
      cancelCallback(existingCallbackNode);
    }
    root.callbackNode = null;
    root.callbackPriority = NoLane;
    return NoLane;
  }

  // 3. 确定优先级并调度
  if (includesSyncLane(nextLanes)) {
    // 同步 lanes → 不通过 Scheduler(微任务结束时会同步刷新)
    if (existingCallbackNode !== null) {
      cancelCallback(existingCallbackNode);
    }
    root.callbackPriority = SyncLane;
    root.callbackNode = null;
    return SyncLane;
  } else {
    const existingCallbackPriority = root.callbackPriority;
    // 从 nextLanes 取出当前最高优先级单条车道作为任务优先级
    const newCallbackPriority = getHighestPriorityLane(nextLanes);
    if (
      newCallbackPriority === existingCallbackPriority
    ) {
      return newCallbackPriority;
    } else {
      cancelCallback(existingCallbackNode);
    }
    // 并发 lanes → 通过 Scheduler 调度
    let schedulerPriorityLevel;
    switch (lanesToEventPriority(nextLanes)) {
      // Discrete/Continuous(点击、输入、滚动)
      case DiscreteEventPriority:
      case ContinuousEventPriority:
        // 用户阻塞,优先执行
        schedulerPriorityLevel = UserBlockingSchedulerPriority;
        break;
      case DefaultEventPriority:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
      case IdleEventPriority:
        schedulerPriorityLevel = IdleSchedulerPriority;
        break;
    }
    const newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performWorkOnRootViaSchedulerTask.bind(null, root),
    );
    root.callbackPriority = newCallbackPriority;
    root.callbackNode = newCallbackNode;
    return newCallbackPriority;
  }
}

作用

  • 标记饥饿过期车道:遍历 root.pendingLanes,对比当前时间戳,把长期得不到执行、被高优任务持续抢占的饥饿车道标记为过期 expiredLanes。过期车道会被优先处理,防止低优更新永久阻塞
  • 计算当前根需要处理的 nextLanes
    • 开启新版副作用调度,当前根有待执行 useEffect:直接使用 pendingPassiveEffectsLanes,把刷副作用逻辑并入本次渲染调度,不再单独创建独立 Scheduler 任务
    • 否则获取下一个要处理的 lanes
  • 无任务(根无任何待处理更新) / 渲染挂起(当前根正在渲染,因 Suspense 等待数据暂停) / 提交挂起(存在中断未完成的提交流程 - 视图过渡、媒体 suspend):取消现存调度任务,清空根任务标记,返回无车道
  • 车道包含同步 SyncLane,且当前不是服务端预渲染场景,保证事件处理(如 click)的即时反馈
    • 同步渲染统一在微任务末尾, flushSyncWorkAcrossRoots_impl 同步执行,不需要交给 Scheduler 分片调度
    • 取消旧异步任务,标记优先级为 SyncLanecallbackNode 置空
  • 异步并发:对比新旧任务优先级,决定复用、取消重建 Scheduler 任务
    • 优先级相等:复用现有任务,直接返回,不重建
    • 优先级升高:取消旧任务,创建新高优任务抢占
    • 映射 Lane 优先级到 Scheduler 调度优先级
    • 创建新 Scheduler 渲染宏任务,回填 root.callbackNode / root.callbackPriority,返回最高优先级车道

设计意义

  • 并发调度饥饿防护机制:自动识别长期得不到执行的低优车道并标记过期,保证多优先级混合更新不会出现永久阻塞,避免频繁高优交互导致列表 / 异步数据低优更新卡死
  • 渲染与 useEffect 副作用共用一套调度任务,减少独立碎片任务
  • 同优先级多次 setState 复用同一个 Scheduler 任务,避免频繁销毁重建
  • 高优更新自动抢占:新交互高优更新取消低优旧任务,保证交互响应
  • 解耦两层优先级模型 :隔离 React 内部 Lane 车道体系与 Scheduler 调度优先级,便于单独调整调度分片策略

五、performWorkOnRootViaSchedulerTask

Scheduler 渲染任务的顶层入口回调,并发分片渲染的启动函数

ini 复制代码
function performWorkOnRootViaSchedulerTask(root, didTimeout) {
  // 全局存在未完成异步提交流程:视图过渡 View Transition、Suspense 媒体延迟提交、未收尾被动副作用队列
  if (hasPendingCommitEffects()) {
    root.callbackNode = null;
    root.callbackPriority = NoLane;
    return null;
  }

  const originalCallbackNode = root.callbackNode;
  const didFlushPassiveEffects = flushPendingEffectsDelayed();
  if (didFlushPassiveEffects) {
    if (root.callbackNode !== originalCallbackNode) {
      return null;
    }
  }
  const workInProgressRoot = getWorkInProgressRoot();
  const workInProgressRootRenderLanes = getWorkInProgressRootRenderLanes();
  const rootHasPendingCommit =
    root.cancelPendingCommit !== null || root.timeoutHandle !== noTimeout;
  const lanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
    rootHasPendingCommit,
  );
  if (lanes === NoLanes) {
    return null;
  }
  
  const forceSync = !disableSchedulerTimeoutInWorkLoop && didTimeout;
  performWorkOnRoot(root, lanes, forceSync);

  scheduleTaskForRootDuringMicrotask(root, now());
  if (root.callbackNode != null && root.callbackNode === originalCallbackNode) {
    return performWorkOnRootViaSchedulerTask.bind(null, root);
  }
  return null;
}

作用

  • 存在未完成异步提交流程,清空当前根的调度任务标记,直接终止本次渲染任务
  • 统一执行所有被动副作用 useEffect,若执行 useEffect 时内部触发高优先级 setState,会取消当前低优调度任务、新建更高优任务,callbackNode 发生变化,直接 return 终止旧渲染
  • 重新计算最新待渲染车道 nextLanes,合并过期车道、过滤 Suspense 阻塞车道、剔除正在渲染的交错更新,无任何待处理更新则直接结束任务。因为 Scheduler 会把多个回调合并进同一个浏览器宏任务,宏任务执行间隙不会让出微任务队列,在调度创建任务到任务实际执行之间,同步代码可能新增 setState 更新,旧车道数据已失效,必须重新读取最新 root.pendingLanes
  • 区分强制同步 / 分片并发,启动真实渲染循环
  • 渲染完成后重新评估剩余更新,生成续执行任务
    • 复用调度函数更新任务状态
    • 返回绑定自身的函数:Scheduler 会在下一个空闲时间片自动续跑剩余渲染
    • 任务被取消 / 无剩余车道:返回 null,本次渲染任务彻底结束

设计意义

  • 完整可中断分片续跑体系:渲染完成后自动评估剩余更新,通过返回函数实现分片续算;单次时间片仅执行少量渲染工作,主动让出主线程
  • 防低饿死机制 :检测到 Scheduler 分配的时间片耗尽,当前车道已过期,必须一次性完整同步渲染,不可中断让出主线程,避免低优更新永久饥饿

六、flushSyncWorkAcrossRoots_impl

全局批量同步渲染入口,微任务末尾统一执行,专门处理同步车道 SyncLane、手势过渡、popstate 同步 Transition 这类需要同步阻塞渲染的任务

ini 复制代码
function flushSyncWorkAcrossRoots_impl(
  syncTransitionLanes,
  onlyLegacy,
) {
  if (isFlushingWork) {
    return;
  }

  if (!mightHavePendingSyncWork) {
    return;
  }

  let didPerformSomeWork;
  isFlushingWork = true;
  do {
    didPerformSomeWork = false;
    let root = firstScheduledRoot;
    while (root !== null) {
      if (onlyLegacy && (disableLegacyMode || root.tag !== LegacyRoot)) {
      } else {
        if (syncTransitionLanes !== NoLanes) {
          const nextLanes = getNextLanesToFlushSync(root, syncTransitionLanes);
          if (nextLanes !== NoLanes) {
            didPerformSomeWork = true;
            performSyncWorkOnRoot(root, nextLanes);
          }
        } else {
          const workInProgressRoot = getWorkInProgressRoot();
          const workInProgressRootRenderLanes =
            getWorkInProgressRootRenderLanes();
          const rootHasPendingCommit =
            root.cancelPendingCommit !== null ||
            root.timeoutHandle !== noTimeout;
          const nextLanes = getNextLanes(
            root,
            root === workInProgressRoot
              ? workInProgressRootRenderLanes
              : NoLanes,
            rootHasPendingCommit,
          );
          if (
            (includesSyncLane(nextLanes) ||
              (enableGestureTransition && isGestureRender(nextLanes))) &&
            !checkIfRootIsPrerendering(root, nextLanes)
          ) {
            didPerformSomeWork = true;
            performSyncWorkOnRoot(root, nextLanes);
          }
        }
      }
      root = root.next;
    }
  } while (didPerformSomeWork);
  isFlushingWork = false;
}

function performSyncWorkOnRoot(root: FiberRoot, lanes: Lanes) {
  const didFlushPassiveEffects = flushPendingEffects();
  if (didFlushPassiveEffects) {
    return null;
  }
  const forceSync = true;
  performWorkOnRoot(root, lanes, forceSync);
}

作用

  • 全局防重入锁判断 :如果已经在同步渲染中,直接 return,阻断递归重入。同步渲染过程中可能触发 setState 再次进入本函数,锁机制防止无限递归栈溢出,是同步渲染安全屏障
  • 无同步任务直接退出
  • 循环遍历全局调度根链表,批量同步渲染所有符合条件的根
    • 仅处理 legacy 旧根过滤
    • 存在同步过渡车道 syncTransitionLanespopstate 路由跳转),需要同步渲染 useTransition 保证页面滚动位置不丢失:调用getNextLanesToFlushSync 提取当前根绑定的同步过渡车道,存在待渲染过渡车道,调用 performSyncWorkOnRoot 同步阻塞渲染,标记 didPerformSomeWork = true,触发外层 do-while 循环兜底
    • 普通同步车道 / 手势过渡同步渲染:调用通用 getNextLanes 计算当前根待处理车道,当车道包含同步 SyncLane 或手势过渡车道(拖拽、滚动等高优手势)且当前根不是服务端预渲染环境(预渲染强制并发分片,禁止阻塞主线程),同步执行 performSyncWorkOnRoot,标记 didPerformSomeWork = true,触发外层 do-while 循环兜底

设计意义

  • 双层循环闭环 :兜底同步渲染内部新增的同步更新,解决同步 setState 嵌套闭环,一轮循环清空所有同步任务
  • 同步渲染周期全局锁:全程阻止重入,保证同步渲染时序完整,杜绝递归死循环
相关推荐
YFF菲菲兔5 小时前
commitRoot 源码解析
react.js
光影少年1 天前
react批量更新、同步/异步更新场景
前端·react.js·掘金·金石计划
YFF菲菲兔1 天前
completeRoot 源码解析
react.js
光影少年2 天前
React 合成事件机制、和原生事件区别、事件冒泡阻止
前端·react.js·掘金·金石计划
YFF菲菲兔2 天前
finishConcurrentRender 源码解析
react.js
YFF菲菲兔2 天前
reconcileChildren 源码解析
react.js
还有多久拿退休金3 天前
Ant Design Tree 搜索定位避坑指南:虚拟滚动下如何实现高亮与精准定位
前端·react.js
光影少年3 天前
react 原理与进阶
前端·react.js·掘金·金石计划
饼饼饼3 天前
React19 状态解惑:State 没那么神秘,一文读懂 React 状态不可变原则与 Hooks 底层链表
前端·react.js