一、面试题:React Fiber 是什么?为什么要引入 Fiber?
核心思路(一句话)
Fiber 本质是把 React 渲染工作拆成可中断、可恢复、可调度的任务,从而让 React 能按优先级控制渲染时机。
主要矛盾
旧版 React 的问题不是"虚拟 DOM 慢",而是:
一次更新过程中,大量 Reconciliation 工作在 JS 主线程上连续执行,无法及时让出主线程。
例如:
text
用户点击
↓
setState
↓
同步执行大量组件更新
↓
Diff
↓
生成 DOM 操作
↓
JS 长任务持续占用主线程
↓
浏览器无法及时处理输入 / 动画 / 绘制
↓
页面卡顿
Fiber 将其改造成:
text
状态更新
↓
创建/标记更新任务
↓
Scheduler 调度优先级
↓
Fiber Reconciliation
↓
可中断 / 可恢复 / 可重新开始
↓
生成副作用 Flags
↓
Commit
↓
真实 DOM 更新
二、面试题:Fiber 只是为了解决"大列表渲染卡顿"吗?
核心思路(一句话)
不是,大列表只是 Fiber 能力的一个应用场景;Fiber 真正解决的是 React 渲染工作的"可调度性"。
Fiber 带来的核心能力:
text
Fiber
├── 工作单元化
├── 可中断
├── 可恢复
├── 可重新开始
├── 优先级调度
├── 并发渲染
├── Suspense
├── Transitions
└── 更细粒度地控制 Render / Commit
所以面试时不要回答:
"Fiber 就是用来做时间切片的。"
更准确的回答是:
Fiber 把 React 的渲染过程拆成 Fiber 工作单元,使 React 可以暂停、恢复、丢弃和重新执行 Render 工作,并结合 Scheduler 对不同更新进行优先级调度。
三、面试题:Fiber 如何实现时间切片?
核心思路(一句话)
把原本连续执行的大量 Fiber 工作拆成多个小工作单元,每处理一部分就检查是否应该让出主线程。
Fiber 为什么天然适合拆分?
传统递归:
text
render(A)
└─ render(B)
└─ render(C)
└─ render(D)
└─ ...
如果递归过程很深:
text
开始
↓
A
↓
B
↓
C
↓
D
↓
...
↓
全部完成
中途很难暂停。Fiber 把节点组织成类似链表的结构:
text
Fiber
├── child
├── sibling
└── return
例如:
text
A
/ \
B C
/ \ \
D E F
可以转换成:
text
A
↓ child
B
↓ child
D
↓ sibling
E
↓ return
B
↓ sibling
C
↓ child
F
...
因此 React 可以:
text
performUnitOfWork(A)
↓
performUnitOfWork(B)
↓
performUnitOfWork(D)
↓
检查是否需要让出
↓
暂停
↓
之后继续 E
四、面试题:时间切片是不是意味着 React 一定不会卡顿?
核心思路(一句话)
不是。时间切片只能让 React 的 Render 工作具备让出主线程的能力,无法阻止其他同步 JavaScript 长任务,也无法让 Commit 阶段无限制地中断。
这是非常重要的追问。例如:
javascript
while (true) {
// 同步 JS 长任务
}
浏览器主线程已经被你的 JS 占满:
text
你的同步 JS
████████████████████████████████
↑
React 无法运行
React 的调度器根本没有机会执行。
所以:
React Fiber 能调度 React 自己的工作,但不能抢占任意同步 JavaScript。
五、面试题:哪些工作可以被中断?哪些工作不能被中断?
核心思路(一句话)
Render 阶段可以被中断、恢复甚至丢弃;Commit 阶段需要保持 UI 更新的一致性,因此不能像 Render 一样随意中断。
Render 阶段
主要工作:
text
组件执行
↓
Reconciliation
↓
创建 / 复用 Fiber
↓
Diff
↓
计算 Flags
↓
形成新的 Fiber Tree
特点:
text
可中断
可恢复
可重新开始
旧 Render 结果可以被丢弃
Commit 阶段
主要工作:
text
Before Mutation
↓
Mutation
↓
Layout
例如:
text
DOM 插入
DOM 删除
DOM 属性修改
useLayoutEffect
ref 更新
Commit 必须保证:
一旦开始提交,就要让 UI 从旧状态一致地过渡到新状态。
因此不能简单理解成:
text
Render:可中断
Commit:不可中断
更准确是:
Render 是可协作调度的计算阶段;Commit 是需要保持 UI 一致性的同步提交阶段。
六、面试题:React 从 setState 到真实 DOM 更新,完整链路是什么?
核心思路(一句话)
一次更新可以概括为:Update → Scheduler → Render/Reconciliation → Fiber Tree → Flags → Commit → DOM。
完整流程图
text
用户操作 / 网络数据 / setState
↓
创建 Update
↓
加入 UpdateQueue
↓
调度 Root 更新
↓
Scheduler
↓
根据优先级安排任务
↓
Render 阶段开始
↓
BeginWork / CompleteWork
↓
Reconciliation
↓
创建 / 复用 Fiber
↓
标记 Flags
↓
构建新的 Fiber Tree
↓
Render 完成
↓
Commit 阶段
↓
┌───────────┼────────────┐
↓ ↓ ↓
Before Mutation Layout
Mutation
↓ ↓ ↓
读取 DOM 修改 DOM 执行 Layout
相关信息 插入/删除/更新 Effects
↓
浏览器后续绘制
这里最容易混淆的是:
text
Render ≠ DOM 更新
Render 主要是在计算:
"最终应该变成什么样。"
Commit 才是真正:
"把计算结果应用到真实 DOM。"
七、面试题:BeginWork 和 CompleteWork 分别做什么?
核心思路(一句话)
BeginWork 负责"向下处理节点、计算子树",CompleteWork 负责"向上完成节点、收集完成信息"。
可以记成:
text
BeginWork = 往下
CompleteWork = 往上
1. BeginWork
主要职责:
text
当前 Fiber
↓
处理组件更新
↓
执行组件逻辑
↓
Reconciliation
↓
生成 / 复用 children
↓
返回下一个 child
例如:
text
A
├── B
└── C
大致:
text
BeginWork(A)
↓
产生 B、C
↓
BeginWork(B)
↓
BeginWork(C)
2. CompleteWork
当子节点处理完后向上返回:
text
D
↑
CompleteWork(D)
↑
CompleteWork(B)
↑
CompleteWork(A)
它主要负责:
text
完成当前 Fiber
↓
处理 Host Component
↓
准备 DOM 相关信息
↓
处理 / 汇总 Flags
↓
向父节点返回
最终形成:
text
A
/ \
B C
/ \
D E
BeginWork
↓
A → B → D → E → C
CompleteWork
↑
D → E → B → C → A
八、面试题:Fiber 为什么使用 child / sibling / return,而不是普通树结构?
核心思路(一句话)
Fiber 使用"child + sibling + return"把树转换成可逐步遍历的结构,使 React 可以把每个 Fiber 作为独立工作单元进行调度。
javascript
fiber.child
fiber.sibling
fiber.return
含义:
text
child → 第一个子节点
sibling → 下一个兄弟节点
return → 父节点
例如:
text
A
/ | \
B C D
/ \
E F
Fiber 关系:
text
A.child = B
B.sibling = C
C.sibling = D
B.child = E
E.sibling = F
B.return = A
C.return = A
D.return = A
E.return = B
F.return = B
这使 React 可以使用:
text
向下:child
横向:sibling
向上:return
完成非递归式的工作循环。
九、面试题:React 如何判断 Fiber 节点应该复用还是创建?
核心思路(一句话)
核心比较条件是同层级节点的 key + type;匹配则复用 Fiber,不匹配则创建新 Fiber。
例如:
jsx
<div key="1" />
更新:
jsx
<div key="1" />
通常可以复用。
但是:
jsx
<div key="1" />
更新成:
jsx
<span key="1" />
类型发生变化:
text
key 相同
type 不同
↓
不能直接复用
↓
删除旧节点
创建新节点
列表中:
jsx
items.map(item => (
<Item key={item.id} />
))
key 的核心意义就是:
帮助 React 判断同层级节点的身份,从而尽可能复用 Fiber。
十、面试题:Fiber 的 Flags 是什么?
核心思路(一句话)
Flags 是 Fiber 上记录"这个节点在 Commit 阶段需要执行什么操作"的标记。
Render 阶段:
text
发现变化
↓
Fiber.flags = 某些操作标记
Commit 阶段:
text
读取 flags
↓
执行对应 DOM 操作
可以理解成:
text
Render:
"我发现这个节点需要更新"
↓
Flags:
"UPDATE"
↓
Commit:
"真正修改 DOM"
常见概念包括:
text
Placement
Update
ChildDeletion
Ref
Passive
Layout
...
实际版本中的 Flags 集合会随着 React 内部实现变化,因此面试不要死背某一版本的完整常量表。
十一、面试题:React 的同步更新和并发更新有什么区别?
核心思路(一句话)
核心区别不是"有没有多线程",而是 React 是否允许这次 Render 工作被中断、延后、重新开始,并根据优先级安排执行。
同步更新
可以理解成:
text
更新发生
↓
尽快完成 Render
↓
Commit
并发更新
可以理解成:
text
更新
↓
进入调度系统
↓
根据优先级安排
↓
开始 Render
↓
发现更高优先级任务
↓
暂停当前工作
↓
处理高优先级任务
↓
之后继续 / 重新计算低优先级任务
注意:
React Concurrent Rendering 不是多线程并行执行 React 代码。
它主要仍然运行在:
text
JavaScript 主线程
所谓"并发"更接近:
多个工作之间可以交错推进,而不是多个 CPU 线程同时执行。
十二、面试题:useTransition 到底改变了什么?
核心思路(一句话)
useTransition 的核心不是"让代码执行更快",而是把某些状态更新标记为非紧急更新,让 React 可以以较低优先级、可中断的方式处理。
例如:
javascript
const [isPending, startTransition] = useTransition();
function handleChange(value) {
setInput(value);
startTransition(() => {
setSearch(value);
});
}
这里:
text
setInput
↓
紧急更新
↓
优先响应用户输入
setSearch
↓
Transition 更新
↓
可以延后 / 中断
所以:
text
useTransition
不是:
"把 JS 执行速度提高"
而是:
"改变更新的调度优先级与可中断性"
十三、面试题:为什么 React 优先级无法解决同步阻塞 JavaScript?
核心思路(一句话)
因为 React Scheduler 本身也运行在 JavaScript 主线程上,主线程被同步长任务占满时,Scheduler 根本没有执行机会。
例如:
javascript
startTransition(() => {
setState(...);
});
while (true) {
// 阻塞主线程
}
此时:
text
JS 主线程
██████████████████████████████
↑
同步任务一直占用
Scheduler
× 无法执行
React
× 无法获得执行机会
浏览器输入
× 无法及时响应
所以性能优化不能只盯着 React:
text
React 调度优化
+
业务 JS 长任务优化
+
组件渲染优化
+
DOM 数量优化
+
浏览器渲染优化
十四、面试题:React Fiber 会不会出现"饥饿"问题?
核心思路(一句话)
会。低优先级任务如果长期被高优先级更新打断,就可能长期得不到执行,因此 React 的调度系统需要处理任务过期、优先级提升等问题。
抽象理解:
text
高优先级任务
↓
执行
高优先级任务
↓
执行
高优先级任务
↓
执行
低优先级任务
↓
一直等待
这就是:
text
Starvation
饥饿
React Scheduler 会通过任务优先级、expiration 等机制避免低优先级任务无限等待。
十五、面试题:Suspense 和 Fiber 调度是什么关系?
核心思路(一句话)
Suspense 本身不是 Scheduler,但它依赖 Fiber 的并发渲染能力,让 React 能够在子树暂时无法完成时暂停这部分 UI,并继续处理其他工作。
抽象流程:
text
Render
↓
进入 Suspense 子树
↓
子树暂时无法完成
↓
Suspense 捕获挂起状态
↓
显示 fallback
↓
其他高优先级工作继续
↓
数据准备完成
↓
重新调度
↓
重新 Render
↓
Commit 正常 UI
所以不要说:
"Suspense 就是 Fiber 的一个 API。"
更准确:
Suspense 是 React 的 UI 协调机制,它建立在 Fiber 的可中断、可恢复渲染能力之上。
十六、面试题:Strict Mode 为什么会看到组件执行两次?
核心思路(一句话)
开发环境下 React Strict Mode 会主动进行额外的调用/挂载检查,以暴露不安全的副作用和不纯逻辑;不能简单理解成"Fiber 把组件构建了两遍"。
例如开发环境可能看到:
text
组件函数执行
↓
再次执行
或者:
text
Effect
↓
cleanup
↓
Effect
其目的主要是帮助发现:
text
不纯渲染
错误副作用
缺失 cleanup
依赖外部可变状态
注意:
不要把 Strict Mode 的开发行为直接等同于:
text
生产环境真的执行两遍
这是面试中的常见陷阱。
十七、面试题:为什么时间切片不是"切得越细越好"?
核心思路(一句话)
切片本身有调度、保存状态、恢复执行等开销,过度拆分会增加调度成本,真正目标是让长任务获得合理的让出机会,而不是无限细分。
可以理解成:
text
任务太大
↓
主线程长时间阻塞
↓
卡顿
但是:
text
任务切得过碎
↓
频繁调度
↓
上下文切换 / 调度开销增加
↓
整体吞吐下降
所以优化目标是:
text
不是:
"切得越碎越好"
而是:
"在响应性与吞吐量之间取得平衡"
十八、面试题:Fiber 任务和浏览器的渲染帧是完全对齐的吗?
核心思路(一句话)
不是。Fiber 的工作调度与浏览器帧调度有关联,但不是"一次 Fiber = 一帧",React 也不能保证每个 Fiber 工作单元严格对应一个浏览器帧。
应该理解成:
text
React Scheduler
↓
安排 React 工作
↓
JavaScript 主线程
↓
浏览器事件循环
↓
样式计算
↓
布局
↓
绘制
React 需要尽可能避免长期占满主线程,但:
text
React Scheduler ≠ 浏览器渲染器
二者属于不同层次。
十九、面试题:为什么 Commit 阶段不能像 Render 一样随便中断?
核心思路(一句话)
因为 Render 是"计算结果",Commit 是"修改外部世界";如果 Commit 随意中断,DOM 可能处于半更新状态,破坏 UI 一致性。
例如:
text
旧 DOM
A B C
Commit:
text
删除 A
修改 B
插入 D
如果执行到:
text
删除 A
修改 B
突然暂停:
text
A ❌
B ✅
C
D ❌
此时 UI 处于中间状态。
因此 React 的设计是:
text
Render
↓
可以暂停
↓
计算出完整结果
↓
Commit
↓
一次性完成必要的 DOM 提交
这也是为什么面试中一定要区分:
Render 可中断 ≠ 整个 React 更新过程都可中断。
二十、最终:React Fiber 五层面试追问图
text
React Fiber
│
┌───────────────┼────────────────┐
↓ ↓ ↓
第一层 第二层 第三层
基础概念 调度机制 Fiber 原理
│ │ │
为什么需要 Fiber 优先级 BeginWork
时间切片是什么 同步/并发 CompleteWork
是否一定不卡顿 useTransition child/sibling/return
│ │ Flags
└───────────────┼────────────────┘
↓
第四层
并发陷阱
│
┌─────────┼─────────┐
↓ ↓ ↓
Suspense StrictMode 饥饿
│
↓
第五层
浏览器性能模型
│
┌─────────┼──────────┐
↓ ↓ ↓
主线程 Render Commit
│ │ │
长任务 可中断 不可随意中断
│
↓
浏览器最终绘制
满分答案:面试时直接这样答
React Fiber 本质上是 React 对渲染工作的重新组织和调度机制。它把组件树转换成 Fiber 工作单元,使 Render 阶段可以被中断、恢复、重新开始,并结合 Scheduler 根据更新优先级进行调度。
一次更新大致经历 Update → Scheduler → Render/Reconciliation → BeginWork → CompleteWork → Flags → Commit → DOM。
其中 Render 阶段主要负责计算新的 Fiber Tree,可以被中断;BeginWork 向下处理组件和子节点,CompleteWork 向上完成节点并处理相关信息;Flags 则记录 Commit 阶段需要执行的 Placement、Update、Deletion 等操作。
Render 完成后进入 Commit,真正执行 DOM 修改以及相关 Effects。Commit 为了保证 UI 一致性,不能像 Render 一样随意中断。
所以 Fiber 不只是解决大列表卡顿,也不等于时间切片。它真正解决的是 React 渲染工作的可调度性,为 Concurrent Rendering、Transition、Suspense 等能力提供了基础。
另外,Fiber 并不是多线程,React Scheduler 也不能抢占任意同步 JavaScript。如果主线程被业务代码的长任务占满,React 本身也无法获得执行机会。
这道题真正要抓住的主线
text
Fiber
↓
工作单元化
↓
Render 可中断
↓
Scheduler 控制优先级
↓
Reconciliation 计算新 Fiber Tree
↓
Flags 记录变化
↓
Render 完成
↓
Commit 同步提交
↓
真实 DOM
面试真正的分水岭不是背"Fiber = 时间切片",而是能不能把这条链路讲完整,并解释清楚:为什么 Render 能中断、为什么 Commit 不能随意中断、优先级到底改变了什么,以及 React 为什么仍然无法解决任意同步 JS 长任务。