Lanes(车道优先级模型)

React 18 引入的优先级模型,用位掩码(bitmask)表示更新的优先级级别。替代了 React 16/17 的 expirationTime 模型,支持多个不同优先级的更新并行处理和批量合并。

核心概念

Lanes 是 React 18 的更新优先级模型。每个更新被分配一个 lane(车道),lane 用位掩码表示——一个 32 位整数中的某一位或某几位。不同的 lane 代表不同的优先级,调度器每次从待处理的 lanes 集合中挑选最高优先级的子集来处理。

text
SyncLane            = 0b0000000000000000000000000000010  // 最高优先级
InputContinuousLane = 0b0000000000000000000000000001000  // 连续输入(拖拽等)
DefaultLane         = 0b0000000000000000000000000100000  // 默认(setTimeout 等)
TransitionLane1     = 0b0000000000000000000001000000000  // startTransition 包裹的更新
IdleLane            = 0b0100000000000000000000000000000  // 空闲时处理

为什么用位掩码而不是简单的数字优先级? 位运算天然支持集合操作:lanes & SyncLane 判断是否包含同步更新,lanes | TransitionLane 合并优先级,lanes & ~processedLanes 排除已处理的优先级——这些操作都是单条 CPU 指令,极其高效。

与 expirationTime 的对比

React 16/17 用 expirationTime(过期时间)表示优先级——数值越大优先级越高。这个模型的问题是无法表示"同时处理多个不连续的优先级级别"。例如,想同时处理 SyncLane 和某个 TransitionLane 但跳过 DefaultLane——用连续的时间范围无法表达这种"跳跃选择",但位掩码天然支持。

在调度流程中的位置

text
setState() → 分配 lane(基于触发上下文)
→ markUpdateLaneFromFiberToRoot(沿 Fiber 树向上标记 lanes)
→ ensureRootIsScheduled(调度器入口)
→ 选择本次要处理的 lanes 子集(getNextLanes)
→ 选择 workLoopSync 或 workLoopConcurrent
→ render 阶段只处理选中的 lanes 对应的更新

lane 的分配规则

  • 离散事件(click、keydown)→ SyncLane(最高优先级,同步执行)
  • 连续事件(mousemove、scroll)→ InputContinuousLane
  • startTransition 包裹的更新 → TransitionLane(可被更高优先级打断)
  • setTimeout、网络请求回调 → DefaultLane

startTransition 的本质

startTransition(() => setState(newValue)) 的核心作用是将包裹的状态更新从 SyncLane 降级到 TransitionLane。降级后的更新:

  • 可被 SyncLane 的更新打断(用户输入不被阻塞)
  • 时间切片下每个 5ms chunk 结束时可让出主线程
  • 如果渲染结果被更新的高优先级更新覆盖,可以直接丢弃未完成的 workInProgress 树

常见失效场景:如果 transition 内的组件树没有被 <Suspense> 包裹,降级后的更新在 commit 阶段仍然不可中断——commit 阶段的 DOM 操作是同步的,时间切片只作用于 render 阶段。

与其他框架调度模型的对比

框架调度模型优先级支持
React 18+Lanes + Fiber 时间切片多级优先级,可中断
Vue 3微任务队列 + 组件深度排序无优先级,FIFO 去重
Solid同步 batch无优先级,无调度

React 需要如此复杂的调度模型,根源在于其响应式粒度——没有细粒度追踪意味着单次更新工作量可能很大,必须支持优先级调度和可中断渲染来保证交互响应性。Solid/Svelte 的细粒度更新天然工作量小,同步执行即可。

引用本术语的文章