文章

优先级调度机制深度解析

优先级调度机制深度解析

一句话概括

React 18 的 Lane 优先级模型用 32 位二进制位掩码表示更新任务的紧急程度——SyncLane(点击输入)的更新可以打断 TransitionLane(过渡渲染),而饥饿保护确保低优先级任务最终一定会执行。

核心知识点

1. Lane:32 位 = 32 条优先级车道

1
2
3
4
5
6
const SyncLane            = 0b000000000000000000000000000001; // 位 0:用户点击、flushSync
const InputContinuousLane = 0b000000000000000000000000000100; // 位 2:拖拽、滚动
const DefaultLane         = 0b000000000000000000000000010000; // 位 4:普通 setState
const TransitionLanes     = 0b000000000000111111111111000000; // 位 6-15:startTransition
const RetryLane           = 0b000000010000000000000000000000; // 位 22:Suspense 重试
const IdleLane            = 0b010000000000000000000000000000; // 位 30:空闲时

高的位 = 低的优先级。最右边最低位是 SyncLane——优先级最高。

2. 位运算:O(1) 的优先级操作

1
2
3
4
5
6
7
8
9
10
11
// 合并多个 Lane
root.pendingLanes = SyncLane | DefaultLane; // 0b10001

// 提取最高优先级(经典 trick:n & -n)
getHighestPriorityLane(lanes) // lanes & -lanes → 0b00001 (SyncLane!)

// 判断当前车道是否是某组的子集
isSubsetOfLanes(set, subset) // (set & subset) === subset

// 移除某条 Lane
removeLane(set, lane) // set & ~lane

lanes & -lanes 是二进制中提取最右 1 的经典位操作,React 用它快速找到当前最高优先级任务。

3. 优先级继承与传播

每次 setState 不只是标记当前 fiber 的 lanes,还沿 return 链向上冒泡到根:

1
2
3
4
5
6
7
8
function markUpdateLaneFromFiberToRoot(fiber, lane) {
  fiber.lanes |= lane;
  let node = fiber.return;
  while (node) {
    node.childLanes |= lane; // 父节点知道子树有更新
    node = node.return;
  }
}

childLanes 让父组件在 beginWork 时快速判断是否需要深入——如果子树上没有任何更新,直接 bailout。

4. 饥饿保护:低优先级不会永远等

每次渲染周期的 getNextLanes 会检查是否有等待过久的 Lane:

1
2
3
if (expirationTime <= currentTime) {
  root.expiredLanes |= lane; // 过期!提升为同步执行
}

过期后的 Lane 不再让位于高优先级更新,直接以 SyncLane 的优先级一口气执行完。

5. startTransition 的”可中断”语义

1
2
3
4
setInputValue(text);               // SyncLane:输入框立刻更新
startTransition(() => {
  setSearchResults(filter(text));  // TransitionLane:结果列表可被中断
});
  • 如果用户在过渡渲染期间继续输入,新的 SyncLane 更新会中断 TransitionLane 渲染
  • workInProgress 树被丢弃,基于最新 current 树重新开始
  • 老的低优先级渲染完全不消耗用户的等待时间

「其实你每天都在用」

  1. React 18 自动批处理:同一事件里的多次 setState 合并成一次渲染,靠 Lane 的位运算合并 lanes |= lane 实现。

  2. useDeferredValue 防止输入卡顿query 随输入即时更新,deferredQuery 只在空闲时更新,搜索结果不阻塞键盘响应。

  3. Suspense 的 fallback 切换:数据加载中的组件抛出 Promise,被 Suspense 捕获,渲染优先级降低,fallback 立即显示。

  4. requestAnimationFrame 不受影响:React 的时间切片在 5ms 后主动让出,rAF 回调有充足时间执行,CSS 动画不卡。

  5. 开发工具 Profiler:React DevTools Profiler 中同一帧内的灰色间隔就是优先级切换点。

常见误解 (FAQ)

❌ 误区 1:”setState 优先级越高越好”

大部分更新就应该用 DefaultLane(普通 setState)。只有用户直接交互才走 SyncLane。如果把所有更新都标为同步,就是退回到 React 15。

❌ 误区 2:”TransitionLane 就是 setTimeout”

startTransition 不是延迟执行——它立即标记 Lane,然后在 Scheduler 的优先级队列中排队。可以被高优先级的点击事件中断,setTimeout 做不到。

❌ 误区 3:”React 18 所有更新都是并发(concurrent)的”

SyncLane(用户输入、flushSynccreateRoot 首屏渲染)始终同步执行,不经过 Scheduler 的时间切片。只有非同步 Lane 才走并发调度。

❌ 误区 4:”改变 Lane 数字就能改变优先级”

Lane 是内部使用的固定位掩码,不应该被外部修改。调整优先级的正确方式是使用 startTransition / useDeferredValue 等 API。

一句话总结

Lane 模型的精髓在于把”优先级”变成了”位”,把”调度”变成了”位运算”——32 个二进制刻度,撑起了 React 并发模式的所有能力。

本文由作者按照 CC BY 4.0 进行授权

© 独行的风. 保留部分权利。

本站采用 Jekyll 主题 Chirpy

本站总访问量 本站访客数 本文阅读量