react 切片 和 优先级调度

关键点在于,这个决策并 不是 在 workLoop (工作循环) 内部 自己判断的。 workLoop 只是一个"埋头干活的工人",而真正的"项目经理"是 Scheduler(调度器)Lane(车道) 模型 的组合。 下面我们来拆解一下 React 是如何区分这两种情况的。

两种不同的"触发机制"

我们不应该把 workLoop 想象成一个需要猜测当前处境的函数,而应该理解为有两条完全不同的路径,都会最终导向"干活"这一步。

路径一:时间片用完(优雅地暂停)这是并发渲染中的"常规路径"。

  1. 准备工作 :调度器有一个低优先级的任务。它通过 requestIdleCallback 向浏览器申请"绿灯"(空闲时间)。
  2. 开始工作 :浏览器回应:"你现在有 4 毫秒的空闲时间。" 于是调度器启动 workLoopConcurrent()
  3. 执行工作 : while 循环开始执行: while (workInProgress !== null && !shouldYield())
    • performUnitOfWork 被调用,处理一个 Fiber 节点。
    • shouldYield() 被调用。它 唯一的职责 就是检查这 4 毫秒的"最后期限"是否已到。它对任务优先级一无所知。
  4. 暂停工作 :假设处理了 20 个 Fiber 节点后,4 毫秒用完了。 shouldYield() 现在返回 true
  5. 做出决策 : while 循环的条件 !shouldYield() 变为 false 。循环 优雅地终止 。
  6. 最终结果 : workLoopConcurrent 函数执行完毕。 workInProgress 指针完好无损,依然指向第 21 个待处理的 Fiber 节点。调度器注意到工作还没完成,于是它会预约下一次的 requestIdleCallback ,以便将来继续。 在这个场景里,循环是由其 内部 的 shouldYield() 时间检查来停止的。这是一个有计划的、合作式的暂停。

路径二:高优先级任务插队,强制中断这是确保 UI 响应的"紧急路径"。

  1. 当前状态 : workLoopConcurrent() 正在愉快地执行一个低优先级的更新(比如我们上面的例子)。
  2. "入侵者"出现 :用户点击了一个按钮。事件处理器触发,调用了 setState
  3. 发出信号 : scheduleUpdateOnFiber 函数被调用。它获得了一个 高优先级的车道 (例如 SyncLane )。它把这个 SyncLane 添加到 Fiber 根节点的 pendingLanes (待处理车道)上。
  4. "项目经理"被唤醒 :调度器收到了这个变更通知。它立刻检查所有任务的优先级,发现:
    • 当前正在运行的车道 ( workInProgressRootRenderLanes ):是低优先级的(例如 TransitionLane )。
    • 新加入的待处理车道 ( pendingLanes ):包含一个高优先级的 SyncLane 。
  5. 做出决策 :调度器当前运行的 workLoop 之外,在更高的层级 做出了关键判断。它看到 SyncLane 的优先级高于 TransitionLane 。它决定 必须中断 当前的工作。
  6. 执行中断 :
    • 它不会礼貌地等待 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 时,满足以下任一条件:

  • 条件 1forceSync = true

    • ReactDOM.flushSync() 强制触发的更新,必须立即同步执行。
  • 条件 2includesBlockingLane(lanes) = true

    • 更新属于高优先级 "阻塞车道"(如 用户输入、点击等 交互事件),需立即响应。
  • 条件 3includesExpiredLane(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、未明确分类事件 异步或非用户直接交互事件 并发渲染(可中断)

关键流程

  1. 事件捕获:所有浏览器事件先被 React 统一监听器捕获(而非直接触发用户写的回调)。
  2. 优先级分配getEventPriority 通过 switch 语句判断事件类型,分配对应优先级。
  3. 调度渲染:高优先级(离散事件)走同步渲染,低优先级(连续 / 默认事件)走并发渲染。

记忆要点

  • VIP 事件(点击、输入等)→ 高优先级 → 同步渲染(不卡顿)。
  • 高频事件(滚动、鼠标移动等)→ 中优先级 → 可中断渲染(保流畅)。
  • 其他事件 → 低优先级 → 并发渲染(不阻塞)。

这个机制从源头区分任务轻重,是 React 并发模式流畅运行的核心基础。

相关推荐
洞窝技术2 小时前
Next.js 不只是前端框架!我们用它搭了个发布中枢,让跨团队协作效率翻倍
前端·next.js
带着梦想扬帆启航2 小时前
UniApp 多个异步开关控制教程
前端·javascript·uni-app
小高0072 小时前
JavaScript 内存管理是如何工作的?
前端·javascript
是大林的林吖2 小时前
解决 elementui el-cascader组件懒加载时存在选中状态丢失的问题?
前端·javascript·elementui
鹏仔工作室2 小时前
elemetui中el-date-picker限制开始结束日期只能选择当月
前端·vue.js·elementui
一 乐2 小时前
个人博客|博客app|基于Springboot+微信小程序的个人博客app系统设计与实现(源码+数据库+文档)
java·前端·数据库·spring boot·后端·小程序·论文
sTone873753 小时前
Android Room部件协同使用
android·前端
晴殇i3 小时前
前端代码规范体系建设与团队落地实践
前端·javascript·面试
用户74054639943093 小时前
Vite 库模式输出 ESM 格式时的依赖处理方案
前端·vite