看 React 源码或者技术文章的时候,经常会遇到 Fiber 这个词:
- Fiber 到底是什么?为什么 React 要搞出这么个东西?
- 都说 Fiber 让渲染可以"中断",具体是怎么实现的?
- Fiber 和 Hooks、Suspense、Concurrent Mode 这些特性有什么关系?
这篇文章会把 Fiber 架构从头到尾讲清楚,包括它的设计动机、数据结构、工作流程,以及它是如何支撑 React 18/19 那些"并发特性"的。
为什么需要 Fiber?
Stack Reconciler 的困境
React 16 之前用的是 Stack Reconciler(栈调和器)。名字来源于它的工作方式------依赖 JavaScript 的调用栈来递归处理组件树。
当你调用 setState() 的时候,React 会从根节点开始,递归遍历整棵组件树,计算出哪些节点需要更新,然后一次性把变更应用到 DOM 上。
这个过程有个致命问题:同步且不可中断。
假设你有一个包含 1000 个节点的列表,用户在输入框里打了个字,触发了状态更新。React 必须一口气把这 1000 个节点都 diff 完、更新完,才能把控制权还给浏览器。在这期间:
- 用户输入没有响应(输入框卡住)
- 动画掉帧(因为主线程被占用)
- 整个页面感觉"卡顿"
问题的根源在于:所有更新都被当成同等优先级处理。用户的输入响应、动画渲染、后台数据更新,在 Stack Reconciler 眼里都一样------必须按顺序执行,谁也不能插队。
Fiber 的解题思路
2015 年,Facebook 开始开发 Fiber,2017 年随 React 16 正式发布。
Fiber 的核心思想是:把不可中断的递归调用,改成可中断的循环遍历。
用 Andrew Clark(React 核心开发者)的话说:
"Fiber 是对调用栈的重新实现,专门为 React 组件设计。你可以把一个 fiber 想象成一个虚拟的栈帧,好处是你可以把这些栈帧保存在内存里,然后随时随地执行它们。"
这段话有点抽象,展开来说就是:
- 把大任务拆成小任务:每个组件的处理变成一个独立的"工作单元"(fiber)
- 每个小任务执行完都可以暂停:检查是否有更紧急的事情要做
- 高优先级任务可以插队:用户输入比后台数据更新重要
- 被中断的任务可以恢复:从上次暂停的地方继续
Fiber 数据结构
Fiber 不只是一个概念,它是一个具体的数据结构。每个 React 组件在内部都对应一个 fiber 节点,这些节点组成一棵树,但不是普通的树------是用链表串联的树。
FiberNode 的关键字段
直接看 React 源码中的 FiberNode 构造函数(简化版):
javascript
function FiberNode(tag, pendingProps, key, mode) {
// ========== 身份标识 ==========
this.tag = tag; // fiber 类型:FunctionComponent、ClassComponent、HostComponent 等
this.key = key; // 用于 diff 的唯一标识
this.type = null; // 组件函数/类,或者 DOM 标签名(如 'div')
this.stateNode = null; // 对应的真实 DOM 节点,或者类组件的实例
// ========== 树结构指针 ==========
this.return = null; // 父节点
this.child = null; // 第一个子节点
this.sibling = null; // 下一个兄弟节点
this.index = 0; // 在兄弟节点中的位置
// ========== 状态相关 ==========
this.pendingProps = pendingProps; // 新的 props(待处理)
this.memoizedProps = null; // 上次渲染用的 props
this.memoizedState = null; // 上次渲染的 state(Hooks 链表也存这里)
this.updateQueue = null; // 状态更新队列
// ========== 副作用 ==========
this.flags = NoFlags; // 副作用标记:Placement、Update、Deletion 等
this.subtreeFlags = NoFlags; // 子树中的副作用标记
this.deletions = null; // 需要删除的子节点
// ========== 调度相关 ==========
this.lanes = NoLanes; // 当前节点的优先级
this.childLanes = NoLanes; // 子树中的优先级
// ========== 双缓冲 ==========
this.alternate = null; // 指向另一棵树中对应的 fiber
}
tag 字段:fiber 的类型标识
tag 决定了 React 如何处理这个 fiber。常见的类型包括:
| tag 值 | 类型名称 | 说明 |
|---|---|---|
| 0 | FunctionComponent | 函数组件 |
| 1 | ClassComponent | 类组件 |
| 3 | HostRoot | 根节点(ReactDOM.createRoot() 创建的) |
| 5 | HostComponent | 原生 DOM 元素,如 <div> |
| 6 | HostText | 文本节点 |
| 7 | Fragment | <React.Fragment> |
| 11 | ForwardRef | React.forwardRef() 创建的组件 |
| 14 | MemoComponent | React.memo() 包装的组件 |
| 15 | SimpleMemoComponent | 简单的 memo 组件 |
React 根据 tag 来决定调用什么方法。比如遇到 FunctionComponent 就执行函数拿返回值,遇到 ClassComponent 就调用 render() 方法。
链表树结构
Fiber 树用三个指针串联:
kotlin
┌─────────────────────────────────────────────┐
│ App │
│ (return: null) │
└─────────────────────────────────────────────┘
│
child
↓
┌─────────────────────────────────────────────┐
│ Header │
│ (return: App) │
└─────────────────────────────────────────────┘
│
child sibling
↓ ↓
┌─────────────────┐ ┌─────────────────┐
│ Logo │ sibling │ Nav │
│ (return: Header)│ ─────────→ │ (return: Header)│
└─────────────────┘ └─────────────────┘
- child:指向第一个子节点
- sibling:指向下一个兄弟节点
- return:指向父节点
为什么用链表而不是数组存子节点?因为链表可以方便地暂停和恢复遍历------只需要记住当前处理到哪个节点就行。
双缓冲机制
Fiber 使用"双缓冲"技术,同时维护两棵树:
- current 树:当前屏幕上显示的内容
- workInProgress 树:正在构建的新树
两棵树的节点通过 alternate 指针互相引用:
scss
current 树 workInProgress 树
┌─────────┐ alternate ┌─────────┐
│ App │ ←─────────────────→│ App │
│(current)│ │ (WIP) │
└─────────┘ └─────────┘
│ │
┌─────────┐ alternate ┌─────────┐
│ Header │ ←─────────────────→│ Header │
│(current)│ │ (WIP) │
└─────────┘ └─────────┘
这个设计的好处:
- 渲染过程中不影响当前显示:所有变更都在 workInProgress 树上进行
- 原子化提交:构建完成后,直接把 workInProgress 变成 current(交换指针)
- 复用 fiber 节点:下次更新时,current 树变成 workInProgress 树的基础,减少内存分配
工作循环:Fiber 如何执行
Fiber 的核心执行逻辑在 workLoop 函数中。整个过程分为两个阶段:Render 阶段 和 Commit 阶段。
整体流程
Render 阶段:构建 workInProgress 树
Render 阶段的目标是:遍历 fiber 树,找出哪些节点需要更新,打上标记(flags),构建完整的 workInProgress 树。
这个阶段是可中断的------React 可以在处理完任意一个 fiber 后暂停,把控制权交还给浏览器。
workLoop:工作循环
javascript
function workLoop() {
// 循环处理每个工作单元
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
// 1. beginWork:处理当前节点,返回子节点
const next = beginWork(current, unitOfWork, renderLanes);
// 2. 更新 memoizedProps
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// 没有子节点了,完成当前节点的工作
completeUnitOfWork(unitOfWork);
} else {
// 有子节点,继续处理子节点
workInProgress = next;
}
}
beginWork:向下遍历,标记更新
beginWork 负责处理当前 fiber 节点,主要做这些事:
- 判断是否可以跳过:如果 props 和 state 都没变,直接跳过这个子树
- 根据 fiber 类型执行不同逻辑:函数组件就执行函数,类组件就调用 render 方法
- 创建/更新子 fiber 节点:对比新旧 children,进行 diff
- 返回第一个子 fiber:作为下一个工作单元
javascript
function beginWork(current, workInProgress, renderLanes) {
// 优化:如果没有更新,可以跳过
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
if (oldProps === newProps && !hasContextChanged()) {
// 没有变化,尝试跳过
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
}
// 根据 fiber 类型分发处理
switch (workInProgress.tag) {
case FunctionComponent:
return updateFunctionComponent(current, workInProgress, renderLanes);
case ClassComponent:
return updateClassComponent(current, workInProgress, renderLanes);
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
// ... 其他类型
}
}
completeWork:向上回溯,准备 DOM
当一个 fiber 节点没有子节点(或所有子节点都处理完了),就会调用 completeWork:
- 创建/更新真实 DOM 节点:但还不挂载到页面上
- 收集副作用:把有 flags 的节点串成链表,方便 Commit 阶段处理
- 冒泡 subtreeFlags:让父节点知道子树中有没有需要处理的副作用
javascript
function completeWork(current, workInProgress, renderLanes) {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case HostComponent: {
const type = workInProgress.type; // 如 'div'
if (current !== null && workInProgress.stateNode !== null) {
// 更新:对比新旧 props,计算需要更新的属性
updateHostComponent(current, workInProgress, type, newProps);
} else {
// 新建:创建 DOM 元素
const instance = createInstance(type, newProps);
// 把所有子 DOM 节点挂到这个元素上
appendAllChildren(instance, workInProgress);
workInProgress.stateNode = instance;
}
// 收集副作用标记到父节点
bubbleProperties(workInProgress);
return null;
}
// ... 其他类型
}
}
遍历顺序示意
假设有这样一棵组件树:
css
App
/ \
Header Main
/ \ \
Logo Nav Content
Fiber 的遍历顺序是深度优先,但会在每个节点上执行 beginWork(向下)和 completeWork(向上):
scss
1. beginWork(App)
2. beginWork(Header)
3. beginWork(Logo)
4. completeWork(Logo) ← Logo 没有子节点,开始 complete
5. beginWork(Nav) ← 回到 Header,处理下一个子节点
6. completeWork(Nav)
7. completeWork(Header) ← Header 所有子节点处理完,complete 自己
8. beginWork(Main)
9. beginWork(Content)
10. completeWork(Content)
11. completeWork(Main)
12. completeWork(App) ← 回到根节点,Render 阶段结束
Commit 阶段:应用变更
Render 阶段完成后,workInProgress 树已经构建好了,所有需要的变更也都标记好了。接下来就是 Commit 阶段------把这些变更实际应用到 DOM 上。
Commit 阶段是同步的、不可中断的。因为这个阶段要操作真实 DOM,必须一次性完成,否则用户会看到不一致的 UI。
Commit 阶段分为三个子阶段:
1. Before Mutation(DOM 操作前)
- 调用
getSnapshotBeforeUpdate生命周期方法 - 调度
useEffect的清理函数(异步)
2. Mutation(执行 DOM 操作)
这是真正修改 DOM 的阶段:
javascript
function commitMutationEffects(root, finishedWork) {
while (nextEffect !== null) {
const flags = nextEffect.flags;
// 处理 DOM 插入
if (flags & Placement) {
commitPlacement(nextEffect);
}
// 处理 DOM 更新
if (flags & Update) {
commitWork(current, nextEffect);
}
// 处理 DOM 删除
if (flags & Deletion) {
commitDeletion(root, nextEffect);
}
nextEffect = nextEffect.nextEffect;
}
}
3. Layout(DOM 操作后)
- 调用
componentDidMount/componentDidUpdate - 调用
useLayoutEffect的回调 - 更新 ref
最后,React 把 current 指针指向 workInProgress 树,完成树的切换。
优先级调度:让重要的事先做
Fiber 架构的一大优势是支持优先级调度。不是所有更新都同等重要------用户输入应该比后台数据刷新更快响应。
Lane 优先级模型
React 18 使用 Lane 模型来表示优先级。每个优先级是一个 32 位整数中的一个位:
javascript
const NoLanes = 0b0000000000000000000000000000000;
const SyncLane = 0b0000000000000000000000000000001; // 最高优先级:同步
const InputContinuousLane = 0b0000000000000000000000000100; // 连续输入,如拖拽
const DefaultLane = 0b0000000000000000000000000010000; // 默认优先级
const TransitionLane1 = 0b0000000000000000000001000000; // Transition
const IdleLane = 0b0100000000000000000000000000000; // 空闲时执行
位运算的好处是可以高效地合并、比较多个优先级:
javascript
// 合并优先级
const mergedLanes = lane1 | lane2;
// 判断是否包含某优先级
const includesLane = (lanes & lane) !== 0;
// 获取最高优先级
const highestLane = lanes & -lanes;
Scheduler:任务调度器
React 有一个独立的 Scheduler 包,负责按优先级调度任务。它的核心思想是时间切片(Time Slicing):
- 把渲染工作拆成多个小任务
- 每个小任务执行完后,检查是否有更高优先级的任务
- 如果有,暂停当前工作,先处理高优先级任务
- 如果时间片用完了(通常是 5ms),让出主线程给浏览器
javascript
function workLoopConcurrent() {
// 循环执行工作单元,但会在时间片用完时暂停
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
function shouldYield() {
// 检查是否还有剩余时间,或者是否有更高优先级的任务
return getCurrentTime() >= deadline || hasHigherPriorityWork();
}
时间切片的效果
没有时间切片的情况下,一个耗时 200ms 的渲染任务会完全阻塞主线程:
css
主线程:[==== 200ms 渲染任务 ====]
^ ^
| |
用户点击 200ms 后才响应
有时间切片后,渲染任务被拆成多个 5ms 的小块,中间可以响应用户交互:
css
主线程:[5ms][响应点击][5ms][5ms][5ms]...[5ms]
^ ^
| |
用户点击 立即响应
startTransition:标记低优先级更新
React 18 提供了 startTransition API,让开发者可以手动标记哪些更新是"可以延迟的":
javascript
import { startTransition } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
function handleChange(e) {
// 高优先级:立即更新输入框
setQuery(e.target.value);
// 低优先级:搜索结果可以稍后更新
startTransition(() => {
setResults(search(e.target.value));
});
}
return (
<>
<input value={query} onChange={handleChange} />
<SearchResults results={results} />
</>
);
}
在这个例子中,用户每次输入都会触发两个更新:
setQuery:高优先级,输入框立即响应setResults:低优先级,包裹在startTransition里,可以被打断
如果用户快速输入,搜索结果的渲染可能会被跳过几次,直到用户停止输入。这保证了输入框始终流畅响应,而不会被搜索结果的渲染阻塞。
Hooks 与 Fiber 的关系
Hooks 是怎么实现的?答案就在 Fiber 节点的 memoizedState 字段里。
Hooks 链表
每个函数组件的 fiber 节点都有一个 memoizedState 字段,存储着该组件所有 hooks 的状态。这些状态以链表的形式串联:
scss
FiberNode
├── memoizedState ─→ Hook1 ─→ Hook2 ─→ Hook3 ─→ null
│ (useState) (useEffect) (useMemo)
└── ...
每个 hook 节点的结构大致如下:
javascript
const hook = {
memoizedState: any, // 这个 hook 存储的值
baseState: any, // 更新前的基础状态
baseQueue: Update | null,
queue: UpdateQueue, // 待处理的更新队列
next: Hook | null // 指向下一个 hook
};
useState 的实现原理
当你在组件里调用 useState(0) 时,React 做的事情:
首次渲染(mount):
javascript
function mountState(initialState) {
// 1. 创建一个新的 hook 节点,挂到链表上
const hook = mountWorkInProgressHook();
// 2. 计算初始值
if (typeof initialState === 'function') {
initialState = initialState();
}
// 3. 保存状态
hook.memoizedState = hook.baseState = initialState;
// 4. 创建更新队列
hook.queue = {
pending: null,
dispatch: null,
// ...
};
// 5. 返回 [state, setState]
const dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, hook.queue);
hook.queue.dispatch = dispatch;
return [hook.memoizedState, dispatch];
}
后续更新(update):
javascript
function updateState() {
// 1. 找到当前 hook(按顺序从链表取)
const hook = updateWorkInProgressHook();
// 2. 处理所有待处理的更新
const queue = hook.queue;
let newState = hook.baseState;
let update = queue.pending;
while (update !== null) {
// 应用每个更新
newState = typeof update.action === 'function'
? update.action(newState)
: update.action;
update = update.next;
}
// 3. 保存新状态
hook.memoizedState = newState;
return [newState, queue.dispatch];
}
为什么 Hooks 不能在条件语句里调用?
因为 hooks 是按调用顺序存储在链表里的,React 靠顺序来匹配每次渲染时的 hook。
假设你这样写:
javascript
function Bad() {
const [count, setCount] = useState(0);
if (count > 0) {
useEffect(() => { /* ... */ }); // 危险!
}
const [name, setName] = useState('');
// ...
}
第一次渲染时 count 是 0,hooks 链表是:
scss
Hook1(useState: count) → Hook2(useState: name) → null
第二次渲染时 count 变成 1,useEffect 被执行了,链表变成:
scss
Hook1(useState: count) → Hook2(useEffect) → Hook3(useState: name) → null
React 按顺序取 hook,第二个位置取到了 useEffect,但代码里第二个 hook 是 useState,类型对不上,直接报错。
所以 React 的规则是:Hooks 只能在函数组件的最顶层调用,不能在条件、循环或嵌套函数里。
useEffect 的实现原理
useEffect 的状态存储方式略有不同,它的副作用会被收集到 fiber 的 updateQueue 中:
javascript
function mountEffect(create, deps) {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
// 标记这个 fiber 有 Passive 副作用
currentlyRenderingFiber.flags |= PassiveEffect;
// 创建 effect 对象,挂到 fiber 的 updateQueue
hook.memoizedState = pushEffect(
HasEffect | Passive,
create,
undefined,
nextDeps
);
}
Effect 链表结构:
javascript
FiberNode.updateQueue.effects
↓
Effect1 → Effect2 → Effect3 → Effect1 (循环链表)
├── create: () => { ... } // 执行的函数
├── destroy: () => { ... } // 清理函数(上次 create 的返回值)
├── deps: [a, b] // 依赖数组
└── next: Effect2
在 Commit 阶段完成后,React 会异步执行所有 Passive 类型的 effects:
- 先执行所有 effect 的
destroy(清理函数) - 再执行所有 effect 的
create(新的副作用)
这也是为什么 useEffect 总是在 DOM 更新后异步执行。
Fiber 支撑的高级特性
Fiber 架构不只是让渲染可以中断,它还是很多 React 高级特性的基础。
Suspense:优雅处理异步
Suspense 让你可以在等待异步数据时显示 fallback UI:
javascript
function ProfilePage() {
return (
<Suspense fallback={<Spinner />}>
<ProfileDetails />
</Suspense>
);
}
function ProfileDetails() {
const user = use(fetchUser()); // 这个 promise 还没 resolve 时会"挂起"
return <h1>{user.name}</h1>;
}
Suspense 的实现依赖 Fiber 的以下能力:
- 抛出 Promise:当组件需要等待异步数据时,可以 throw 一个 Promise
- 捕获并暂停:Fiber 在遍历时捕获这个 Promise,标记当前子树为"挂起"状态
- 显示 fallback:渲染最近的 Suspense 边界的 fallback
- 恢复渲染:Promise resolve 后,从挂起的地方重新开始渲染
用户名
Concurrent Rendering:并发渲染
React 18 的并发模式完全建立在 Fiber 之上。它的核心能力是:React 可以同时准备多个版本的 UI。
比如使用 useTransition:
javascript
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('home');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
return (
<>
<TabButton onClick={() => selectTab('home')}>Home</TabButton>
<TabButton onClick={() => selectTab('posts')}>Posts</TabButton>
<TabButton onClick={() => selectTab('contact')}>Contact</TabButton>
{isPending && <Spinner />}
<TabPanel tab={tab} />
</>
);
}
当用户点击 "Posts" tab 时:
- React 开始在"后台"渲染 Posts 页面(构建 workInProgress 树)
- 同时保持显示当前的 Home 页面(current 树不变)
- 如果 Posts 渲染很慢,用户可以点击其他 tab,之前的渲染会被放弃
- 渲染完成后,React 才会切换到新的 UI
这就是"并发"的含义:多个渲染任务可以同时存在,React 可以在它们之间切换。
Error Boundaries:错误边界
Error Boundaries 也依赖 Fiber 的树结构:
javascript
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
logError(error, info.componentStack);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
当子组件抛出错误时,Fiber 会:
- 沿着
return指针向上查找最近的 Error Boundary - 标记该 Error Boundary 的 fiber 有
ShouldCaptureflag - 在 Commit 阶段调用
componentDidCatch - 重新渲染 Error Boundary,显示降级 UI
Server Components:服务端组件
React Server Components 也依赖 Fiber 架构。服务端渲染时,React 会:
- 在服务端构建 Fiber 树
- 将 Fiber 树序列化成特殊的"Flight"格式
- 客户端接收后,重建 Fiber 树,进行 Hydration
由于 Fiber 是一个可序列化的数据结构(本质上是一棵树),它可以在服务端和客户端之间传输。
实际影响:性能优化建议
理解了 Fiber 架构,可以更好地进行性能优化。
1. 避免频繁创建新对象
每次渲染时创建新对象会导致 props 变化,触发不必要的子组件更新:
javascript
// 差:每次渲染都创建新的 style 对象
function Bad() {
return <div style={{ color: 'red' }}>...</div>;
}
// 好:把常量提到组件外面
const style = { color: 'red' };
function Good() {
return <div style={style}>...</div>;
}
在 Fiber 的 beginWork 阶段,React 会比较新旧 props。如果是同一个引用,可以快速跳过子树的处理。
2. 合理使用 key
key 帮助 React 在 diff 时识别哪些元素改变了位置:
javascript
// 差:用 index 作为 key,列表重排序时会有问题
items.map((item, index) => <Item key={index} {...item} />)
// 好:用稳定的唯一 ID
items.map(item => <Item key={item.id} {...item} />)
Fiber 在 reconcile children 时,会用 key 来复用已有的 fiber 节点,而不是删除再创建。
3. 使用 startTransition 处理大量更新
如果某个操作会触发大量组件更新(比如筛选长列表),用 startTransition 标记为低优先级:
javascript
function FilteredList({ items }) {
const [filter, setFilter] = useState('');
const [filteredItems, setFilteredItems] = useState(items);
function handleFilterChange(e) {
const value = e.target.value;
setFilter(value); // 高优先级:输入框立即响应
startTransition(() => {
// 低优先级:过滤操作可以稍后执行
setFilteredItems(items.filter(item =>
item.name.includes(value)
));
});
}
return (
<>
<input value={filter} onChange={handleFilterChange} />
<ItemList items={filteredItems} />
</>
);
}
4. 使用 useDeferredValue 延迟非关键更新
useDeferredValue 可以让某个值的更新延后:
javascript
function SearchResults({ query }) {
// 这个值会延迟更新,不阻塞输入
const deferredQuery = useDeferredValue(query);
// 基于延迟的值进行渲染
const results = useMemo(
() => slowSearch(deferredQuery),
[deferredQuery]
);
return <ResultList results={results} />;
}
5. Suspense 配合 lazy 做代码分割
javascript
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<Loading />}>
<HeavyComponent />
</Suspense>
);
}
Fiber 会在 HeavyComponent 加载时挂起,显示 Loading,加载完成后自动恢复渲染。
调试 Fiber
React DevTools 可以直接查看 Fiber 树。打开 DevTools 的 Components 面板,你看到的组件树本质上就是 Fiber 树的可视化。
你还可以在浏览器控制台里访问 fiber:
javascript
// 获取某个 DOM 元素对应的 fiber
const fiber = domElement._reactFiber$...; // 键名是动态的
// 查看 fiber 的结构
console.log(fiber.type); // 组件类型
console.log(fiber.memoizedState); // 状态(hooks 链表)
console.log(fiber.memoizedProps); // props
console.log(fiber.child); // 第一个子节点
console.log(fiber.sibling); // 兄弟节点
console.log(fiber.return); // 父节点
总结
React Fiber 是 React 从 16 版本开始的核心架构,它解决了旧版 Stack Reconciler 的几个关键问题:
- 可中断渲染:把递归调用改成循环遍历,每处理完一个 fiber 就可以暂停
- 优先级调度:不同类型的更新有不同优先级,重要的先做
- 并发渲染:可以同时准备多个版本的 UI,按需切换
- 增量渲染:把渲染工作拆成小块,分散到多个帧中
Fiber 的核心是一个链表树结构的数据模型,加上双缓冲技术和两阶段渲染(Render + Commit)。它是 Hooks、Suspense、Concurrent Mode、Server Components 等现代 React 特性的基础设施。
理解 Fiber 不是为了在日常开发中直接操作它,而是为了:
- 理解 React 的工作原理,写出更高效的代码
- 更好地使用
startTransition、useDeferredValue等 API - 排查性能问题时知道从哪里入手
- 读懂 React 源码和技术文章
最后放几个深入学习的资源:
- React Fiber Architecture - Andrew Clark 写的官方说明
- Inside Fiber: in-depth overview of the new reconciliation algorithm in React - Max Koretskyi 的深度解析
- React 源码 - ReactFiber.js - 直接看源码
- React v18.0 发布博客 - 并发特性的官方介绍
如果你觉得这篇文章有帮助,欢迎关注我的 GitHub,下面是我的一些开源项目:
Claude Code Skills (按需加载,意图自动识别,不浪费 token,介绍文章):
- code-review-skill - 代码审查技能,覆盖 React 19、Vue 3、TypeScript、Rust 等约 9000 行规则(详细介绍)
- 5-whys-skill - 5 Whys 根因分析,说"找根因"自动激活
- first-principles-skill - 第一性原理思考,适合架构设计和技术选型
全栈项目(适合学习现代技术栈):
- prompt-vault - Prompt 管理器,用的都是最新的技术栈,适合用来学习了解最新的前端全栈开发范式:Next.js 15 + React 19 + tRPC 11 + Supabase 全栈示例,clone 下来配个免费 Supabase 就能跑
- chat_edit - 双模式 AI 应用(聊天+富文本编辑),Vue 3.5 + TypeScript + Vite 5 + Quill 2.0 + IndexedDB