关键点在于,这个决策并 不是 在 workLoop (工作循环) 内部 自己判断的。 workLoop 只是一个"埋头干活的工人",而真正的"项目经理"是 Scheduler(调度器) 和 Lane(车道) 模型 的组合。 下面我们来拆解一下 React 是如何区分这两种情况的。
两种不同的"触发机制"
我们不应该把 workLoop 想象成一个需要猜测当前处境的函数,而应该理解为有两条完全不同的路径,都会最终导向"干活"这一步。
路径一:时间片用完(优雅地暂停)这是并发渲染中的"常规路径"。
- 准备工作 :调度器有一个低优先级的任务。它通过
requestIdleCallback向浏览器申请"绿灯"(空闲时间)。 - 开始工作 :浏览器回应:"你现在有 4 毫秒的空闲时间。" 于是调度器启动
workLoopConcurrent()。 - 执行工作 : while 循环开始执行:
while (workInProgress !== null && !shouldYield())。performUnitOfWork被调用,处理一个 Fiber 节点。shouldYield()被调用。它 唯一的职责 就是检查这 4 毫秒的"最后期限"是否已到。它对任务优先级一无所知。
- 暂停工作 :假设处理了 20 个 Fiber 节点后,4 毫秒用完了。 shouldYield() 现在返回 true
- 做出决策 : while 循环的条件
!shouldYield()变为false。循环 优雅地终止 。 - 最终结果 :
workLoopConcurrent函数执行完毕。workInProgress指针完好无损,依然指向第 21 个待处理的 Fiber 节点。调度器注意到工作还没完成,于是它会预约下一次的requestIdleCallback,以便将来继续。 在这个场景里,循环是由其 内部 的shouldYield() 时间检查来停止的。这是一个有计划的、合作式的暂停。
路径二:高优先级任务插队,强制中断这是确保 UI 响应的"紧急路径"。
- 当前状态 :
workLoopConcurrent()正在愉快地执行一个低优先级的更新(比如我们上面的例子)。 - "入侵者"出现 :用户点击了一个按钮。事件处理器触发,调用了
setState。 - 发出信号 :
scheduleUpdateOnFiber函数被调用。它获得了一个 高优先级的车道 (例如 SyncLane )。它把这个SyncLane添加到 Fiber 根节点的pendingLanes(待处理车道)上。 - "项目经理"被唤醒 :调度器收到了这个变更通知。它立刻检查所有任务的优先级,发现:
- 当前正在运行的车道 (
workInProgressRootRenderLanes):是低优先级的(例如 TransitionLane )。 - 新加入的待处理车道 (
pendingLanes):包含一个高优先级的 SyncLane 。
- 当前正在运行的车道 (
- 做出决策 :调度器
在当前运行的workLoop 之外,在更高的层级 做出了关键判断。它看到 SyncLane 的优先级高于 TransitionLane 。它决定 必须中断 当前的工作。 - 执行中断 :
- 它不会礼貌地等待 shouldYield() 。
- 它直接调用 prepareFreshStack() 函数。
prepareFreshStack丢弃 了旧的、只做了一半的 workInProgress 树,并为这个高优先级的 SyncLane 更新创建了一个 全新的 workInProgress 树。- 然后,它会立刻开始一个 新的 工作循环(通常是
workLoopSync,这个循环会完全 忽略 shouldYield ,因为它必须一次性执行完毕)。
React 选择 workLoopSync 还是 workLoopConcurrent 的核心逻辑
js
const shouldTimeSlice =
(!forceSync &&
!includesBlockingLane(lanes) &&
!includesExpiredLane(root, lanes)) ||
(enableSiblingPrerendering && checkIfRootIsPrerendering(root, lanes));
决策关键:shouldTimeSlice 变量
- 位置:
react-reconciler/ReactFiberWorkLoop.js - 作用:决定使用同步渲染(
workLoopSync)还是并发渲染(workLoopConcurrent)
1. 执行 workLoopSync(同步渲染)的情况
当 shouldTimeSlice = false 时,满足以下任一条件:
-
条件 1 :
forceSync = true- 由
ReactDOM.flushSync()强制触发的更新,必须立即同步执行。
- 由
-
条件 2 :
includesBlockingLane(lanes) = true- 更新属于
高优先级"阻塞车道"(如用户输入、点击等交互事件),需立即响应。
- 更新属于
-
条件 3 :
includesExpiredLane(root, lanes) = true- 低优先级任务因被
反复打断而 "过期",强制同步执行避免 "饥饿"。
- 低优先级任务因被
2. 执行 workLoopConcurrent(并发渲染)的情况
当 shouldTimeSlice = true 时,满足以下所有条件:
- 非
flushSync触发的更新; - 不属于高优先级阻塞车道;
- 未过期的低优先级任务;
- 典型场景:
startTransition包裹的更新、网络请求后的 UI 更新等。
总结记忆表
| 渲染模式 | 触发场景 | 核心特点 |
|---|---|---|
| workLoopSync | flushSync / 用户交互 / 过期任务 | 同步、不可中断、高优先级 |
| workLoopConcurrent | startTransition / 异步数据更新 / 非紧急任务 | 可中断、低优先级、时间分片 |
React 事件优先级判断机制总结
核心决策者:getEventPriority 函数
- 位置:
react-dom-bindings/src/events/ReactDOMEventListener.js - 作用:根据事件类型分配优先级,决定后续渲染模式(同步 / 并发)
三大优先级分类及对应事件
| 优先级类型 | 对应事件示例 | 特点 | 渲染模式 |
|---|---|---|---|
| DiscreteEventPriority(离散事件优先级) | click、keydown、input、focus、submit | 用户直接、单次交互,需即时反馈 | 同步渲染(workLoopSync) |
| ContinuousEventPriority(连续事件优先级) | scroll、mousemove、touchmove、drag | 高频连续触发,避免阻塞主线程 | 并发渲染(可中断) |
| DefaultEventPriority(默认事件优先级) | load、error、未明确分类事件 | 异步或非用户直接交互事件 | 并发渲染(可中断) |
关键流程
- 事件捕获:所有浏览器事件先被 React 统一监听器捕获(而非直接触发用户写的回调)。
- 优先级分配 :
getEventPriority通过switch语句判断事件类型,分配对应优先级。 - 调度渲染:高优先级(离散事件)走同步渲染,低优先级(连续 / 默认事件)走并发渲染。
记忆要点
- VIP 事件(点击、输入等)→ 高优先级 → 同步渲染(不卡顿)。
- 高频事件(滚动、鼠标移动等)→ 中优先级 → 可中断渲染(保流畅)。
- 其他事件 → 低优先级 → 并发渲染(不阻塞)。
这个机制从源头区分任务轻重,是 React 并发模式流畅运行的核心基础。
