【react18原理探究实践】调度机制之注册任务

254 阅读8分钟

上一篇我们讲到了,root.render()最后调用scheduleUpdateOnFiber注册开始任务调度,本篇我们介绍下scheduleUpdateOnFiber做了什么


scheduleUpdateOnFiber 函数的作用

scheduleUpdateOnFiber 是 React 调度系统的核心入口函数,它负责将更新转换为可执行的任务并进行调度。

函数签名

export function scheduleUpdateOnFiber(
  root: FiberRoot,      // Fiber 根节点
  fiber: Fiber,         // 发生更新的 Fiber 节点
  lane: Lane,           // 更新的优先级
  eventTime: number,    // 事件时间戳
)

主要工作步骤

第一步:安全检查和标记

checkForNestedUpdates(); // 检查嵌套更新
markRootUpdated(root, lane, eventTime); // 标记根节点有更新

第二步:区分渲染阶段更新 vs 正常更新

if (
  (executionContext & RenderContext) !== NoLanes &&
  root === workInProgressRoot
) {
  warnAboutRenderPhaseUpdatesInDEV(fiber);

  workInProgressRootRenderPhaseUpdatedLanes = mergeLanes(
    workInProgressRootRenderPhaseUpdatedLanes,
    lane,
  );
} else {
  if (enableUpdaterTracking) {
    if (isDevToolsPresent) {
      addFiberToLanesMap(root, fiber, lane);
    }
  }
}

第三步:处理中间更新(Interleaved Updates)

if (root === workInProgressRoot) {
  if (
    deferRenderPhaseUpdateToNextBatch ||
    (executionContext & RenderContext) === NoContext
  ) {
    workInProgressRootInterleavedUpdatedLanes = mergeLanes(
      workInProgressRootInterleavedUpdatedLanes,
      lane,
    );
  }
  if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
    markRootSuspended(root, workInProgressRootRenderLanes);
  }
}

第四步:核心调度 - ensureRootIsScheduled

ensureRootIsScheduled(root, eventTime);
console.log("✅ ~ ensureRootIsScheduled:", ensureRootIsScheduled)
  • ensureRootIsScheduled 是整个函数的 核心

  • 它负责:

    1. 确定下一个要处理的优先级(getNextLanes

    2. 检查是否可以重用现有任务

    3. 根据优先级选择调度策略:

      • 同步更新scheduleSyncCallback / scheduleLegacySyncCallback
      • 并发更新scheduleCallback 并传入调度器优先级

第五步:处理同步更新的特殊情况

if (
  lane === SyncLane &&
  executionContext === NoContext &&
  (fiber.mode & ConcurrentMode) === NoMode &&
  !(__DEV__ && ReactCurrentActQueue.isBatchingLegacy)
) {
  resetRenderTimer();
  flushSyncCallbacksOnlyInLegacyMode();
}

ensureRootIsScheduled 的核心逻辑

同步调度

if (newCallbackPriority === SyncLane) {
  if (root.tag === LegacyRoot) {
    scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
  } else {
    scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
  }
}

并发调度

else {
  let schedulerPriorityLevel;
  switch (lanesToEventPriority(nextLanes)) {
    case DiscreteEventPriority:
      schedulerPriorityLevel = ImmediateSchedulerPriority;
      break;
    case ContinuousEventPriority:
      schedulerPriorityLevel = UserBlockingSchedulerPriority;
      break;
    case DefaultEventPriority:
      schedulerPriorityLevel = NormalSchedulerPriority;
      break;
    case IdleEventPriority:
      schedulerPriorityLevel = IdleSchedulerPriority;
      break;
    default:
      schedulerPriorityLevel = NormalSchedulerPriority;
      break;
  }
  newCallbackNode = scheduleCallback(
    schedulerPriorityLevel,
    performConcurrentWorkOnRoot.bind(null, root),
  );
}

总结

scheduleUpdateOnFiber 的核心作用

  1. 更新调度入口:将 Fiber 更新转换为可调度任务

  2. 优先级管理:根据 Lane 确定更新优先级

  3. 调度策略选择:

    • 同步更新 → performSyncWorkOnRoot
    • 并发更新 → performConcurrentWorkOnRoot
  4. 任务队列管理:将任务添加到相应调度队列

  5. 中断与恢复:处理渲染过程中的新更新

整体流程

scheduleUpdateOnFiber
        ↓
   ensureRootIsScheduled
        ↓
优先级判断 → 同步/并发调度
        ↓
performSyncWorkOnRoot / performConcurrentWorkOnRoot
        ↓
实际的渲染工作循环

首次 root.render 注册同步任务

首次调用:

root.render(<App />);

流程概览

  1. React 创建 根 Fiber 对应 <App />
  2. React 创建 更新对象(Update)
  3. 调用 enqueueUpdate(fiber, update) 将更新加入 Fiber 更新队列。
  4. 调用:
scheduleUpdateOnFiber(root, root.current, SyncLane, eventTime);

首次渲染使用 SyncLane(同步更新)


scheduleUpdateOnFiber 注册任务的逻辑

  1. 获取下一个要执行的 Lane:
const nextLanes = getNextLanes(root); // 首次渲染只有 SyncLane
  1. 判断同步调度:
if (newCallbackPriority === SyncLane) {
  if (root.tag === LegacyRoot) {
    scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
  } else {
    scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
  }
}
  • ConcurrentRoot(React18+)scheduleSyncCallback(performSyncWorkOnRoot(root))
  • LegacyRoot(React17 及以前)scheduleLegacySyncCallback(performSyncWorkOnRoot(root))

✅ 首次渲染注册的是 同步任务,直接执行 performSyncWorkOnRoot,构建 Fiber 树并提交 DOM。


任务类型总结

场景Root 类型Lane注册任务函数调度方式
首次 render / React18+ConcurrentRootSyncLaneperformSyncWorkOnRoot(root)同步调度
首次 render / React17LegacyRootSyncLaneperformSyncWorkOnRoot(root)同步队列
用户事件触发更新ConcurrentRoot非SyncLaneperformConcurrentWorkOnRoot(root)并发调度

执行顺序

root.render(<App />)
        ↓
enqueueUpdate(fiber, update)
        ↓
scheduleUpdateOnFiber(root, fiber, SyncLane)
        ↓
ensureRootIsScheduled
        ↓
scheduleSyncCallback(performSyncWorkOnRoot(root))
        ↓
执行 performSyncWorkOnRoot → 构建 Fiber 树 → 提交 DOM

⚡ 首次 root.render 不会进入并发队列,它是一个 同步任务,保证页面立即渲染。


小结

  • scheduleUpdateOnFiber 是 React 从 更新到调度任务 的核心函数。
  • 首次 root.render 使用 同步 Lane,注册 performSyncWorkOnRoot 同步任务。
  • 同步任务保证首次渲染立即完成,而并发更新机制从第二次更新开始发挥作用。
  • 了解首次渲染任务机制,有助于理解 React 并发模式与同步渲染的差异

注册task的几个schedule源码剖析

在 React 的内部实现中,任务调度是核心机制之一。React 需要协调同步更新和异步更新,处理不同优先级的任务,并确保良好的用户体验。本文将深入分析 React 中三个关键的调度函数:scheduleSyncCallbackscheduleLegacySyncCallbackscheduleCallback,通过源码解读来理解 React 调度系统的设计思想。

1. 概述:三个调度函数的定位

在 React 的调度体系中,这三个函数承担着不同的职责:

  • scheduleSyncCallback:现代 React 的同步更新调度
  • scheduleLegacySyncCallback:兼容 Legacy 模式的同步更新调度
  • scheduleCallback:基于优先级的异步任务调度

它们的关系可以用这个图来表示:

React 更新调度
│
├─ 同步更新 (SyncLane)
│  ├─ Legacy 模式 → scheduleLegacySyncCallback
│  └─ 现代模式 → scheduleSyncCallback
│  │
│  └─ 内部队列 (syncQueue) + 微任务/宏任务刷新
│
└─ 并发更新 (其他 Lane)
   └─ scheduleCallback → React Scheduler → 优先级调度

2. scheduleSyncCallback:同步任务队列管理

2.1 源码实现

// src/react/packages/react-reconciler/src/ReactFiberSyncTaskQueue.new.js

let syncQueue: Array<SchedulerCallback> | null = null;
let includesLegacySyncCallbacks: boolean = false;
let isFlushingSyncQueue: boolean = false;

export function scheduleSyncCallback(callback: SchedulerCallback) {
  // Push this callback into an internal queue. We'll flush these either in
  // the next tick, or earlier if something calls `flushSyncCallbackQueue`.
  if (syncQueue === null) {
    syncQueue = [callback];
  } else {
    // Push onto existing queue. Don't need to schedule a callback because
    // we already scheduled one when we created the queue.
    syncQueue.push(callback);
  }
}

2.2 核心特点

  1. 队列管理:维护一个内部的 syncQueue 数组
  2. 延迟执行:只是将回调加入队列,不立即执行
  3. 批处理优化:多个同步任务可以在一次刷新中批量处理
  4. 避免重复调度:后续任务直接加入现有队列

2.3 使用场景

// 在 ReactFiberWorkLoop.new.js 中的调用
if (newCallbackPriority === SyncLane) {
  if (root.tag === LegacyRoot) {
    scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
  } else {
    scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root)); // 这里
  }
}

3. scheduleLegacySyncCallback:Legacy 模式适配

3.1 源码实现

export function scheduleLegacySyncCallback(callback: SchedulerCallback) {
  includesLegacySyncCallbacks = true;
  scheduleSyncCallback(callback);
}

3.2 设计目的

这个函数看似简单,但承担着重要的兼容性职责:

  1. 标记 Legacy 模式:设置 includesLegacySyncCallbacks = true
  2. 复用现有逻辑:内部调用 scheduleSyncCallback
  3. 区分处理策略:为不同模式提供差异化的刷新逻辑

3.3 Legacy 模式的特殊处理

export function flushSyncCallbacksOnlyInLegacyMode() {
  // Only flushes the queue if there's a legacy sync callback scheduled.
  if (includesLegacySyncCallbacks) {
    flushSyncCallbacks();
  }
}

这个函数只会在包含 Legacy 回调时才刷新队列,体现了对不同渲染模式的精细控制。

4. scheduleCallback:优先级调度器

4.1 React 中的包装层

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

function scheduleCallback(priorityLevel, callback) {
  if (__DEV__) {
    // If we're currently inside an `act` scope, bypass Scheduler and push to
    // the `act` queue instead.
    const actQueue = ReactCurrentActQueue.current;
    if (actQueue !== null) {
      actQueue.push(callback);
      return fakeActCallbackNode;
    } else {
      return Scheduler_scheduleCallback(priorityLevel, callback);
    }
  } else {
    // In production, always call Scheduler. This function will be stripped out.
    return Scheduler_scheduleCallback(priorityLevel, callback);
  }
}

4.2 Scheduler 的核心实现

// src/react/packages/scheduler/src/forks/Scheduler.js

function unstable_scheduleCallback(priorityLevel, callback, options) {
  var currentTime = getCurrentTime();

  var startTime;
  if (typeof options === 'object' && options !== null) {
    var delay = options.delay;
    if (typeof delay === 'number' && delay > 0) {
      startTime = currentTime + delay;
    } else {
      startTime = currentTime;
    }
  } else {
    startTime = currentTime;
  }

  var timeout;
  switch (priorityLevel) {
    case ImmediatePriority:
      timeout = IMMEDIATE_PRIORITY_TIMEOUT;
      break;
    case UserBlockingPriority:
      timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
      break;
    case IdlePriority:
      timeout = IDLE_PRIORITY_TIMEOUT;
      break;
    case LowPriority:
      timeout = LOW_PRIORITY_TIMEOUT;
      break;
    case NormalPriority:
    default:
      timeout = NORMAL_PRIORITY_TIMEOUT;
      break;
  }

  var expirationTime = startTime + timeout;

  var newTask = {
    id: taskIdCounter++,
    callback,
    priorityLevel,
    startTime,
    expirationTime,
    sortIndex: -1,
  };

  // 任务调度逻辑...
}

4.3 调度机制

scheduleCallback 使用宏任务进行调度,主要通过:

  1. MessageChannel:优先选择,避免 4ms 延迟
  2. setImmediate:Node.js 环境
  3. setTimeout:兜底方案
let schedulePerformWorkUntilDeadline;
if (typeof localSetImmediate === 'function') {
  schedulePerformWorkUntilDeadline = () => {
    localSetImmediate(performWorkUntilDeadline);
  };
} else if (typeof MessageChannel !== 'undefined') {
  const channel = new MessageChannel();
  const port = channel.port2;
  channel.port1.onmessage = performWorkUntilDeadline;
  schedulePerformWorkUntilDeadline = () => {
    port.postMessage(null);
  };
} else {
  schedulePerformWorkUntilDeadline = () => {
    localSetTimeout(performWorkUntilDeadline, 0);
  };
}

5. 同步队列的执行:flushSyncCallbacks

5.1 源码实现

export function flushSyncCallbacks() {
  if (!isFlushingSyncQueue && syncQueue !== null) {
    // Prevent re-entrance.
    isFlushingSyncQueue = true;
    let i = 0;
    const previousUpdatePriority = getCurrentUpdatePriority();
    try {
      const isSync = true;
      const queue = syncQueue;
      setCurrentUpdatePriority(DiscreteEventPriority);
      for (; i < queue.length; i++) {
        let callback = queue[i];
        do {
          callback = callback(isSync);
        } while (callback !== null);
      }
      syncQueue = null;
      includesLegacySyncCallbacks = false;
    } catch (error) {
      // If something throws, leave the remaining callbacks on the queue.
      if (syncQueue !== null) {
        syncQueue = syncQueue.slice(i + 1);
      }
      // Resume flushing in the next tick
      scheduleCallback(ImmediatePriority, flushSyncCallbacks);
      throw error;
    } finally {
      setCurrentUpdatePriority(previousUpdatePriority);
      isFlushingSyncQueue = false;
    }
  }
  return null;
}

5.2 关键特性

  1. 防重入:通过 isFlushingSyncQueue 标志避免重复执行
  2. 优先级设置:使用 DiscreteEventPriority 确保高优先级
  3. 错误处理:出错时保留剩余任务,并通过 Scheduler 恢复
  4. 连续执行:支持回调返回新回调的链式执行

6. 调度策略的选择:微任务 vs 宏任务

6.1 同步更新为什么选择微任务?

ReactFiberWorkLoop.new.js 中:

if (supportsMicrotasks) {
  // Flush the queue in a microtask.
  if (__DEV__ && ReactCurrentActQueue.current !== null) {
    ReactCurrentActQueue.current.push(flushSyncCallbacks);
  } else {
    scheduleMicrotask(() => {
      if (
        (executionContext & (RenderContext | CommitContext)) ===
        NoContext
      ) {
        flushSyncCallbacks();
      }
    });
  }
} else {
  // Flush the queue in an Immediate task.
  scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
}

选择微任务的原因

  1. 更快的执行时机:微任务在当前事件循环末尾立即执行
  2. 符合同步语义:用户调用 flushSync 期望立即看到结果
  3. 避免不必要延迟:不用等待下一个宏任务循环

6.2 并发更新为什么选择宏任务?

并发更新通过 Scheduler 使用宏任务,原因是:

  1. 让出主线程:宏任务之间浏览器可以进行渲染
  2. 支持中断:可以在任务间检查更高优先级的更新
  3. 避免阻塞渲染:防止长时间占用主线程

7. 调度路径分析

7.1 并非所有调度都汇聚到 unstable_scheduleCallback

通过源码分析,我们发现:

调度入口分析:
│
├─ 同步更新路径 (独立系统)
│  ├─ scheduleSyncCallback → syncQueue → 微任务刷新
│  ├─ scheduleLegacySyncCallback → syncQueue → 微任务刷新
│  └─ 错误恢复时 → scheduleCallback → unstable_scheduleCallback
│
└─ 并发更新路径 (Scheduler 系统)
   └─ scheduleCallback → Scheduler_scheduleCallback → unstable_scheduleCallback

7.2 设计优势

这种分离式设计带来的好处:

  1. 同步更新更快:避免 Scheduler 的调度开销
  2. 并发更新更灵活:支持复杂的优先级调度
  3. 错误恢复健壮:同步队列出错可降级到异步恢复

8. 实际使用示例

8.1 同步更新触发

// 用户代码
flushSync(() => {
  setCount(count + 1);
});

// React 内部调用链
// → ensureRootIsScheduled
// → scheduleSyncCallback(performSyncWorkOnRoot)
// → scheduleMicrotask(flushSyncCallbacks)
// → 立即执行更新

8.2 并发更新触发

// 用户代码  
startTransition(() => {
  setLargeList(newData);
});

// React 内部调用链
// → ensureRootIsScheduled  
// → scheduleCallback(NormalPriority, performConcurrentWorkOnRoot)
// → unstable_scheduleCallback
// → 基于优先级调度执行

9. 总结

  1. scheduleSyncCallbackscheduleLegacySyncCallback 构成了 React 的同步更新系统,使用内部队列管理,通过微任务快速刷新

  2. scheduleCallback 构成了 React 的异步调度系统,基于 Scheduler 实现优先级调度和时间切片

  3. 微任务和宏任务的选择不是任意的,而是基于具体场景的权衡:同步更新追求速度,并发更新追求流畅

参考

  • React 源码:packages/react-reconciler/src/ReactFiberWorkLoop.new.js
  • React 源码:packages/react-reconciler/src/ReactFiberSyncTaskQueue.new.js
  • React 源码:packages/scheduler/src/forks/Scheduler.js