👨🏫 本系列由前端面试真题博主 Kincy 发起,每日更新一题,通勤路上轻松掌握高频知识点。
📢 如果你想第一时间获取更新,或与群友交流面试经验、内推信息,欢迎加入微信群(文末扫码)!
📌 系列前言:
面试题千千万,我来帮你挑重点。每天一道,通勤路上、蹲坑时、摸鱼中,技术成长不设限!本系列主打幽默 + 深度 + 面霸必备语录,你只管看,面试场上稳拿 offer!
👮♂️ 面试官杀来:
React 的更新机制你知道吧?Fiber 架构你了解过?
那你说说,React 是怎么做到“可中断渲染”的?Scheduler 起了什么作用?
🥊 我反击道:
哼哼,React 的调度系统早就不是 setTimeout 那个水平了。
有请本场 MVP —— 🧠 Scheduler,出场!
🧠 Scheduler 的真本事:它是 React 的“任务调度中心”
Scheduler 干的事很像一个精明的 小助理,每天盯着老板(浏览器)的空闲时间,把 React 的活分优先级安排好,一点点干。
✳️ 1. “时间切片”利器:requestIdleCallback 太慢,React 自己造轮子
Scheduler 不靠 requestIdleCallback(太慢太不稳定),
它直接用 MessageChannel 或 postMessage 异步调度任务,
创建时间切片(time slice) ,一帧一帧干活,做到:
✅ 不阻塞页面交互
✅ 渲染过程中也能中断 / 暂停 / 恢复!
🎚️ 2. 六种优先级,谁紧谁先上:
Scheduler 内部任务有六种优先级,从高到低如下:
| 优先级 | 说明 | 常见场景 |
|---|---|---|
| Immediate | 立刻干 | 比如同步 setState |
| UserBlocking | 用户操作相关 | 输入框、按钮点击 |
| Normal | 默认优先级 | 异步渲染 |
| Low | 非关键任务 | 日志收集、预加载 |
| Idle | 有空再做 | 性能监控、统计任务 |
| NoPriority | 暂无优先级 | 一般内部使用 |
Scheduler 会根据优先级,判断是否抢占当前任务,
实现类似操作系统里的“协作式多任务调度”。
💣 面试官加码:“那 Scheduler 和 Fiber 有啥关系?”
我端起咖啡,轻轻一笑:
Scheduler 负责 安排谁来上班,Fiber 负责 具体怎么干活。
- Scheduler:任务调度层 → "安排谁来干"
- Fiber:任务执行层 → "具体怎么干"
一句话:Scheduler 负责说话,Fiber 负责搬砖。
📦 举个例子:模拟一个 React 任务调度流程
import { unstable_scheduleCallback, unstable_NormalPriority } from 'scheduler';
unstable_scheduleCallback(unstable_NormalPriority, () => {
console.log('我是在未来被调度执行的');
});
这个代码干了啥?
👉 把一个任务按照“正常优先级”安排进了调度队列,等待浏览器空闲时执行。
🛠️ React 内部是怎么用 Scheduler 的?
React 在 render 阶段:
- 调用
scheduleUpdateOnFiber()安排一次更新 - 通过 Scheduler 判断任务优先级
- 如果有更高优先级任务插队,当前任务就会 被中断
- 直到有空,再继续上次的渲染
🧠 核心模块:scheduler 包就是调度引擎!
🔧 最重要的两个函数:
// packages/scheduler/src/Scheduler.js
unstable_scheduleCallback(priorityLevel, callback)
unstable_shouldYield() // 判断是否该中断当前任务
Scheduler 就是靠这两位大哥完成:
unstable_scheduleCallback:安排任务进调度队列unstable_shouldYield:判断时间片是否到了,是不是该让位了
🔬 看一下源码内部做了啥?
function workLoop(hasTimeRemaining, initialTime) {
let currentTime = initialTime;
currentTask = peek(taskQueue); // 取出优先级最高的任务
while (currentTask !== null) {
if (
currentTask.expirationTime > currentTime &&
(!hasTimeRemaining || shouldYieldToHost())
) {
// ⛔️ 当前任务没超时,但时间片到了,先中断
break;
}
const callback = currentTask.callback;
// 执行任务!
const continuationCallback = callback();
if (typeof continuationCallback === 'function') {
// 如果任务返回的是 continuation,说明没干完,继续等下次
currentTask.callback = continuationCallback;
} else {
// 干完了,出队
pop(taskQueue);
}
currentTask = peek(taskQueue);
}
}
🎯 核心逻辑:
- 每次执行前判断
shouldYieldToHost(),如果返回true,就中断任务;- 每个任务可以是可恢复的,如果返回一个函数,就说明“我下次接着干”。
🤔 那这个 shouldYieldToHost 又是啥?
function shouldYieldToHost() {
return getCurrentTime() >= deadline;
}
非常简单粗暴 —— 看当前时间是不是超了时间片的 deadline。
这个 deadline 是哪来的?
👇 来自 startWorkLoopTimer():
deadline = currentTime + yieldInterval;
默认 yieldInterval = 5ms,也就是说,每个任务最多干 5ms,就要“让位”,防止卡住主线程!
⚙️ 是谁调用了 Scheduler?
别忘了,Scheduler 本身是 通用调度器,不是只为 React 服务。
React 本体中,在 ReactFiberWorkLoop 模块中这样用它👇
import {
unstable_scheduleCallback,
unstable_NormalPriority,
} from 'scheduler';
scheduleCallback(NormalPriority, performConcurrentWorkOnRoot);
这个 performConcurrentWorkOnRoot 是干啥的?
它会触发 Fiber 的开始工作:
function performConcurrentWorkOnRoot(root, didTimeout) {
renderRootConcurrent(root, lanes); // 触发渲染
}
再往下就是熟悉的 beginWork -> completeWork -> commitRoot 流程了。
🔁 React 中断恢复怎么做的?
比如你点个按钮,React 会判断这个更新的优先级(Lane),然后:
- 推入任务队列(scheduleUpdateOnFiber)
- Scheduler 安排 performConcurrentWorkOnRoot
- 执行渲染时,每 5ms 判断要不要中断
- 如果被中断,就保留 workInProgress 指针
- 下一次恢复从中断位置继续!
React 就像个装修队:施工 5 分钟,听听业主有啥新需求,有就暂停,先干新需求。
🎚️ 优先级系统(Lane Model)和 Scheduler 的关系?
Scheduler 管宏观调度(谁先干),Lane 是 React 自己的优先级系统(干啥先)。
| 层级 | 作用 | 举例 |
|---|---|---|
| Scheduler 优先级 | 控制 task 调度节奏 | 点击任务优先于日志任务 |
| React Lane 优先级 | 控制 fiber 树更新顺序 | setState 优先于 transition |
🤹♂️ 面霸语录(限时使用)
“React 的并发模式靠的是调度优先级 + 时间切片,Scheduler 是它的 CPU 调度器。”
“Fiber 是双缓冲链表结构,Scheduler 让它按优先级轮流上台演出。”
“你 setState 的时候,Scheduler 已经开始盘算你要不要等一等了。”
🧠 总结一句话:
Scheduler 让 React 渲染变得像打王者荣耀:能插队、能暂停,还能随时换人上场!
🚀 明日预告
面试官:React 的 concurrent rendering 到底怎么运作的?我:Fiber 架构和 Lane 模型,再来整一套!
📌 点赞 + 收藏 + 关注系列!
📚 本系列每天一题,持续更新中!
👉 扫码加入【前端面试题交流群】,一起成长、交流、内推、分享机会!
备注“掘金”优先通过