一、核心思路(一句话)
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 和优先级调度。