面试题:React 中 Lane 和 Scheduler 是怎么配合的?

一、核心思路(一句话)

Lane 用来表示更新的优先级,Scheduler 负责按照优先级安排任务什么时候执行;Render 过程中如果发现更高优先级的工作,就可以暂停当前 Render,先处理更重要的更新。


二、Lane 的优先级有哪些?

核心思路(一句话)

Lane 本质上是一组用位表示的更新优先级,React 根据不同 Lane 判断哪些更新应该优先处理。

常见的几类可以这样理解:

Lane 常见场景 优先级
SyncLane flushSync 等需要立即处理的更新 最高
InputDiscreteLane 点击、键盘输入等离散交互 高
InputContinuousLane 滚动、拖拽等连续交互 较高
DefaultLane 普通 setState 普通
TransitionLanes startTransition 中的更新 较低
IdleLane 非紧急、空闲时处理的工作 很低

例如:

js 复制代码
// 普通更新
setCount(count + 1);

// Transition 更新
startTransition(() => {
  setSearchResult(result);
});

可以简单理解成:

text 复制代码
点击按钮
  ↓
高优先级更新
  ↓
优先处理

startTransition
  ↓
Transition 更新
  ↓
可以晚一点处理

这里真正要记住的不是 Lane 名字

真正重要的是:

Lane 给每个更新打上优先级标签,React 根据这个标签决定哪些工作应该先处理。

而且 Lane 不只是简单的"优先级数字"。它还承担把更新进行分组的作用,React 可以通过多个 Lane 的组合判断当前这一轮应该处理哪些更新。


三、Scheduler 是干什么的?

核心思路(一句话)

Scheduler 就是负责安排任务什么时候执行,并根据优先级决定先执行哪个任务。

可以把它和 Lane 分开理解:

text 复制代码
Lane
 ↓
这个更新有多重要?
 ↓
Scheduler
 ↓
什么时候执行这个任务?
 ↓
React Work Loop
 ↓
具体处理哪些 Fiber?

所以:

Lane 解决"谁更重要",Scheduler 解决"什么时候做"。


四、Scheduler 底层是怎么工作的?

核心思路(一句话)

Scheduler 会维护待执行的任务,根据优先级选择任务执行,并通过时间切片让任务执行一段时间后主动让出主线程。

整体过程:

text 复制代码
React 收到更新
      ↓
给更新分配 Lane
      ↓
根据优先级安排 Scheduler 任务
      ↓
Scheduler 维护任务队列
      ↓
选择应该执行的任务
      ↓
进入 React Render
      ↓
一个个处理 Fiber
      ↓
检查是否应该让出
      ↓
   ┌──┴──┐
   ↓     ↓
  否      是
   ↓      ↓
继续    停止当前工作
          ↓
       安排下一次任务
          ↓
       当前 Task 结束
          ↓
       浏览器获得机会
          ↓
       下一次 Scheduler 任务
          ↓
       继续处理剩余 Fiber

五、Scheduler 是怎么"让出主线程"的?

这是面试最容易讲错的地方。

核心思路(一句话)

Scheduler 不是把 React 扔到另一个线程,而是让当前 JavaScript Task 主动结束,然后把下一段工作安排到后续 Task 执行。

例如 React 正在处理:

text 复制代码
Fiber A
Fiber B
Fiber C
Fiber D
Fiber E

执行到 C:

text 复制代码
A ✓
B ✓
C ✓
D ← 下一步
E

React 判断:

js 复制代码
shouldYield()

发现应该让出,于是停止当前工作循环:

text 复制代码
Fiber C
  ↓
shouldYield()
  ↓
停止继续处理
  ↓
当前 React 工作函数返回
  ↓
当前 JavaScript Task 结束

这才是真正的"让出"。 不是浏览器突然把 React 从中间掐掉。


六、MessageChannel 在这里做什么?

核心思路(一句话)

MessageChannel 用来安排下一次 React 工作,让当前 Task 结束后,React 再回来继续处理。

简化代码:

js 复制代码
const channel = new MessageChannel();

channel.port1.onmessage = () => {
  workLoop();
};

function scheduleNext() {
  channel.port2.postMessage(null);
}

React 需要让出时:

js 复制代码
if (shouldYield()) {
  scheduleNext();
  return;
}

于是:

text 复制代码
当前 Task
────────────────────
React Render
Fiber A
Fiber B
Fiber C
shouldYield()
    ↓
postMessage()
    ↓
workLoop 返回
────────────────────
当前 Task 结束


后续 Task
────────────────────
MessageChannel
    ↓
onmessage()
    ↓
workLoop()
    ↓
Fiber D
Fiber E
Fiber F
────────────────────

所以要特别注意:

MessageChannel 本身不会中断 JavaScript。

它做的是:

安排一个后续的 MessageChannel Task。

真正的"暂停"是 React 自己:

text 复制代码
shouldYield()
 ↓
停止工作循环
 ↓
当前 JS Task 结束

七、为什么 MessageChannel 能做到这一点?

因为:

js 复制代码
channel.port2.postMessage(null);

是异步的。它不会立即调用:

js 复制代码
channel.port1.onmessage();

而是向浏览器安排一个后续的消息任务。

所以:

text 复制代码
Task0
│
├── React Render
├── postMessage()
└── Task0结束
       ↓
浏览器继续调度任务
       ↓
MessageChannel 对应的 Task
       ↓
onmessage()
       ↓
React继续Render

这里更准确的术语是 Task,而不是简单说"宏任务"。


八、为什么不是 requestIdleCallback?

这是一个常见误区。

不能简单说:

❌ React Scheduler 通过 requestIdleCallback 实现时间切片。

因为 React 需要更主动地控制自己的调度,而不能完全依赖浏览器什么时候认为"空闲"。

现代 Scheduler 的核心思路是:

text 复制代码
任务队列
 +
任务优先级
 +
时间切片
 +
异步调度下一轮工作

浏览器环境中历史上使用过 MessageChannel 等方式来安排后续任务。

因此面试时最好说:

Scheduler 会通过异步任务把后续工作重新安排到之后执行,浏览器环境中可以使用 MessageChannel 这样的方式实现,而不是简单依赖 requestIdleCallback。


九、高优先级更新来了之后,怎么切换?

把前面的内容全部串起来。 假设现在 React 正在处理:

text 复制代码
TransitionLane
 ↓
低优先级 Render
 ↓
Fiber A
 ↓
Fiber B
 ↓
Fiber C

突然用户点击按钮:

text 复制代码
产生高优先级更新
 ↓
分配更高优先级 Lane

React 发现:

text 复制代码
当前:
TransitionLane

新任务:
更高优先级 Lane

于是:

text 复制代码
低优先级 Render
      ↓
处理 Fiber
      ↓
发现更高优先级工作
      ↓
停止当前 Render
      ↓
Scheduler 优先安排高优先级工作
      ↓
高优先级 Render
      ↓
Commit

注意:

不是把当前 JavaScript 从中间强行切走。

而是:

text 复制代码
当前工作循环
      ↓
在检查点发现更重要的任务
      ↓
主动停止
      ↓
当前 Task 结束
      ↓
Scheduler 后续优先执行高优先级任务

如果之前的低优先级 Render 还没有 Commit,那么已经算出来的那部分结果可以被丢弃,之后根据最新状态重新计算。


十、Fiber、Lane、Scheduler 三者到底是什么关系?

这是整道题最值得记住的一张图:

text 复制代码
                    Update
                       ↓
                     Lane
             "这个更新有多重要?"
                       ↓
                  Scheduler
             "什么时候执行它?"
                       ↓
               React Work Loop
              "现在做哪些工作?"
                       ↓
                    Fiber
              "具体处理哪个节点?"
                       ↓
                 shouldYield()
                       ↓
              是否需要主动让出?
                  ↙         ↘
                否           是
                ↓             ↓
             继续执行      停止当前工作
                              ↓
                       安排下一次 Task
                              ↓
                       当前 Task 结束
                              ↓
                       浏览器获得机会
                              ↓
                     下一次 Task 执行
                              ↓
                       继续 Fiber 工作

最后压缩成三句话

Lane 管"谁更重要"。
Scheduler 管"什么时候做"。
Fiber 把工作拆开,让 React 能够在工作循环中一段一段地推进。

三者结合起来,才有了 React 的可中断 Render 和优先级调度。

相关推荐
liangshanbo12151 小时前
请求拦截器相关面试题
java·前端·javascript
ShawnLiaoking1 小时前
ADS305 Big Data Lecuture4 Web Scraping
大数据·前端
liangshanbo12151 小时前
React面试题:Hooks 为什么不能在分支和循环里面写?
前端·javascript·react.js
FungLeo1 小时前
成为全栈·Next.js 网站前台篇·响应式、可访问性与错误状态:内容站不能只验桌面首页
react.js·响应式设计·next.js·移动端适配·成为全栈·web 可访问性
开开心心就好1 小时前
视频压缩工具推荐,绿色版免安装双击即用
java·前端·人工智能·spring·智能手机·intellij-idea·excel
西红柿炖牛腩3541 小时前
GLB 转 OBJ/PLY 后体积没变?3D 模型不减面变小靠什么
前端·javascript·3d
欣欣之王来了1 小时前
前端开发学习路线图:2026年Vue方向最新版
前端·学习·架构·项目·vue3教程
夏幻灵1 小时前
前端八股:Vue2 与 Vue3 的核心区别:响应式、Composition API 与性能优化
前端