深入 Fiber 内部:Hook 链表结构与 DOM Ref 绑定的奥秘
前言
在前两篇文章中,我们从业务实战角度解决了重复请求和高频并发拦截的问题。我们学到了 useRef 是同步的物理锁,而 useState 是异步的状态快照。然而,作为一个进阶开发者,我们不能止步于"怎么用",更要探究"为什么"。
React 是如何在内存中记住这些 Hook 的?为什么 Hook 必须遵守不能在条件语句中使用的"死律"?<div ref={myRef}> 又是如何在毫秒之间精准捅到真实 DOM 上的?本文将带你潜入 React Fiber 架构的底层,拆解这些核心机制。
1. Hook 的持久化存储:单向链表结构
在 React 内部,每一个函数组件都对应一个 Fiber 对象。Fiber 是 React 更新过程中的最小工作单元,它在内存中持久化存储了组件的所有元数据。
Hook 对象的"串葫芦"模型
当你连续调用多个 Hook 时,React 并不是按名称存储的,而是将它们拼成了一个有序的单向链表。
- Fiber.memoizedState :在函数组件中,它并不直接存值,而是指向这个链表的表头(即第一个 Hook 对象)。
- Hook.next:每个 Hook 对象内部都有一个指针,指向下一个 Hook。

为什么 Hook 不能写在 if 语句中?
因为 React 查找 Hook 的唯一证据是执行顺序 。如果第二渲染时某个 if 导致少了一个 Hook 调用,整个链表的顺序就会错位,React 会把原本属于索引 3 的状态强行套在索引 2 上,导致内存数据彻底混乱。
2. 核心概念辨析:两个 memoizedState
在查阅底层原理时,经常会看到两个同名的属性,必须严加区分:
-
Fiber.memoizedState:
- 角色:链表管理员。
- 存储内容:指向整个 Hook 链表的第一个节点。
-
Hook.memoizedState:
- 角色:数据存储员。
- 存储内容:该 Hook 对应的具体数据。
- 注意 :对于
useState,这里存的是状态值;对于useRef,这里存的是对象{ current: ... }。
3. 渲染的两阶段:从"画稿"到"施工"
理解调度逻辑的关键在于区分 React 的两个核心运行阶段:
阶段 A:渲染阶段 (Render Phase)
- 核心动作:计算差异(Diffing)。
- 特点 :纯计算,不操作 DOM。
- 状态流转 :
- 调用函数组件。
- 遇到
useState,去 Hook.queue 里看有没有未处理的更新任务。 - 计算出新值,并作为渲染快照返回。
此时,如果你直接通过 Ref 访问 DOM,你会发现它是 null 或旧的,因为"施工队"还没进场。
阶段 B:提交阶段 (Commit Phase)
- 核心动作:将计算结果应用到真实浏览器视图。
- 关键细节 :这是一个同步且不可中断的过程,分为三个子阶段:
- Before Mutation:准备工作。
- Mutation :真正的 DOM API 调用(如
appendChild)。 - Layout :此时 DOM 已经挂载。这是 Ref 赋值的发生时刻。
4. Ref 绑定 DOM 的内部链路
当你写下 <div ref={myRef}> 时,内部经历了如下逻辑:
- 标记阶段 (Render) :React 在构建虚拟 DOM 时,发现该元素携带了
ref属性,会在对应的 Fiber 节点上打一个名为Ref的标志位(Flags)。 - 赋值阶段 (Commit) :
- 当施工队(Commit 流程)完成 DOM 节点的创建后,会检查标志位。
- React 内部通过类似于
fiber.ref.current = fiber.stateNode的操作,强行将真实的 DOM 元素地址写入你提供的useRef对象中。
- 通知阶段 :随后触发
useEffect或useLayoutEffect回调,此时开发者可以安全地操作 DOM。

5. 总结:第一性原理的终点
通过这三篇文章的探索,我们终于摸清了 React 状态管理的底层逻辑:
- 稳定性源于顺序:Hook 的链表结构决定了调用的顺序不可动摇。
- 更新源于计算 :
useState的异步源于它需要经历 Render 到 Commit 的完整闭环计算。 - Ref 是后门 :
useRef既是绕过渲染周期的同步变量,又是 React 在 Commit 阶段留给开发者的 DOM 访问接口。
掌握了 Fiber 内部的"串葫芦"模型和两阶段提交机制,你将不仅能修复表面的 Bug,更能在架构设计时预判数据的流向与风险。这就是从业务开发迈向技术专家的核心内功。