Time Slicing(时间切片)
React Fiber 架构的核心调度机制,将渲染工作拆成约 5ms 的小块(work unit),每个块结束后检查是否有更高优先级任务需要插队,从而避免长时间渲染阻塞主线程。本质上是用调度复杂度补偿响应式粒度不足导致的高更新成本。
核心概念
Time Slicing(时间切片)是 React Fiber 架构中的并发调度机制。它将 render 阶段的工作拆成多个小块(约 5ms 为一个调度窗口),每个 work unit 完成后检查 shouldYield()——如果有更高优先级的工作(如用户输入事件)需要处理,当前渲染暂停让出主线程,待高优先级工作完成后再恢复。
本质:React 没有细粒度响应式追踪,setState 触发的组件重执行 + VDOM diff 可能涉及大量计算。时间切片的设计逻辑是——"既然每次更新都可能很重,那至少把重活拆开做,别一口气卡住主线程"。
工作机制
Fiber 链表使"可中断"成为可能
传统递归式 VDOM diff(React 15 的 Stack Reconciler)无法暂停——调用栈一旦开始就必须走完。React 16 引入的 Fiber 架构将组件树从递归结构改为链表结构(child → sibling → return 指针),遍历变成循环迭代,中断点就是循环的暂停位置。
两种工作循环
调度器根据当前处理的 Lanes 优先级选择循环模式:
workLoopSync:包含SyncLane(如用户点击等离散事件)时使用,不可中断,一口气跑完workLoopConcurrent:不含SyncLane时使用,每个performUnitOfWork结束后检查shouldYield()
// 并发模式的工作循环(简化)
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
shouldYield 的判断机制
shouldYield() 并非硬编码 5ms 阈值,而是基于 MessageChannel 的回调时机来判断——通过 MessageChannel 发消息给自己,当消息回调触发时标记为"该让出了"。实际的时间窗口受浏览器和设备性能影响,5ms 只是经验近似值。
双缓冲保证一致性
React 维护 current 树和 workInProgress 树两棵 Fiber 树(双缓冲)。render 阶段在 workInProgress 树上操作(可中断、可丢弃),commit 阶段原子性地将 workInProgress 切换为 current——保证用户看到的 DOM 始终一致,不会出现"渲染到一半"的中间态。
与 Lanes 优先级模型的协作
时间切片与 Lanes 模型配合工作:
- 每个状态更新被分配一个 Lane(位掩码表示优先级)
SyncLane(最高)→ 用户离散事件(click)→ 同步执行,不切片DefaultLane(中)→ 常规更新 → 可切片TransitionLane(最低)→startTransition包裹的更新 → 可切片,且可被高优先级打断
高优先级更新可以打断正在进行的低优先级渲染——丢弃当前 workInProgress 树,重新开始高优先级渲染。这就是 startTransition 能让输入不卡顿的原理:输入事件走 SyncLane,过滤结果走 TransitionLane,后者可被前者随时打断。
作用范围与局限
时间切片只作用于 render 阶段(组件函数执行 + VDOM diff),不作用于 commit 阶段(DOM 操作)。如果 commit 阶段需要插入大量 DOM 节点(如几千行列表),时间切片无法帮忙——commit 是同步不可中断的。这是一个常被忽视的失效场景。
为什么其他框架不需要时间切片
时间切片的存在是 React 响应式模型的必然推论:
- Solid/Svelte:每次更新只改一个 DOM 节点(Fine-grained Reactivity),单次更新成本极低,不存在"长时间渲染阻塞主线程"的问题
- Vue 3:响应式追踪到组件级,配合 Patch Flags 加速 diff,单次更新成本中等,微任务批处理足够应对
- React:无自动追踪,
setState可能触发整棵子树的重新执行和 diff,单次更新成本高——必须有时间切片来保证 UI 响应性
调度器的复杂度是框架为响应式粒度不足买的单。
引用本术语的文章