React 函数组件转换为真实 DOM 的过程可以分为以下几个关键步骤:
graph LR
A[JSX] --> B[Babel 编译]
B --> C[React 元素对象]
C --> D[虚拟 DOM 树]
D --> E[Fiber Tree]
E --> F[Diff 算法]
F --> G[副作用标记]
G --> H[Commit 阶段]
H --> I[真实 DOM]
一. JSX 转换为 React 元素对象
函数组件中返回的 JSX 代码会被 Babel 编译器 转换为 React.createElement 调用,最终生成一个 React 元素对象(JS 对象) 。
function MyComponent() {
return <div>Hello World</div>;
}
编译后:
function MyComponent() {
return React.createElement("div", null, "Hello World");
}
生成的 React 元素对象结构类似:
{
$$typeof: Symbol(react.element),
type: 'div', // 标签类型
key: null,
ref: null,
props: {
children: 'Hello World'
},
_owner: null,
...
}
二. 生成虚拟 DOM 树
当调用 ReactDOM.render() 或 react-dom/client 的 createRoot().render() 时,React 会执行以下操作:
- 调用函数组件:执行
MyComponent(),得到一个 React 元素对象(虚拟 DOM)。 - 递归构建虚拟 DOM 树:如果组件中包含嵌套的子组件,React 会递归调用所有子组件函数,生成完整的虚拟 DOM 树。
三. 虚拟 DOM 转换为 fiber tree
虚拟 DOM(Virtual DOM)转换为 Fiber Tree 是 协调阶段(Reconciliation Phase) 的核心步骤之一。这一过程将组件树的虚拟表示(虚拟 DOM)转化为 React 内部的链表结构(Fiber Tree),以便支持 可中断的渲染 和 优先级调度。每个虚拟 DOM 节点在 Fiber 架构中被映射为一个 Fiber 节点,并附加了状态和调度信息。
1. 虚拟 DOM 节点
[1]. 定义
虚拟 DOM 是 React 用来描述 UI 状态的一种 内存中的 JavaScript 对象树。它是一个轻量级的、与真实 DOM 类似的结构,用于高效比较和更新真实 DOM。
[2]. 作用
- 构建 UI 的快照:每次组件渲染时,React 会生成一个新的虚拟 DOM 树(如果组件包裹了memo,则根据props是否改变决定是否复用旧的vdom节点)。
[3]. 特点
- 独立于渲染引擎:虚拟 DOM 本身不直接操作真实 DOM。
- 递归结构:每个组件返回的 JSX 最终都会被转换为虚拟 DOM 节点(
ReactElement对象)。
虚拟 DOM 是由 ReactElement 对象组成的树,例如:
{
type: "div",
props: { children: "Hello" },
key: null,
ref: null
}
2. Fiber 节点
[1]. 定义
Fiber 是 React 16 引入的 新协调引擎(Reconciliation Engine) ,用于改进渲染性能和用户体验。它将原本递归式的渲染流程拆分为可中断、可恢复的单元任务。
[2]. 作用
- 支持并发渲染:允许浏览器在渲染过程中处理高优先级任务(如用户输入)。
- 增量渲染:将渲染工作切分为小块,利用浏览器的空闲时间逐步完成。
- 优先级调度:根据任务类型(如动画、输入、渲染)分配不同的优先级。
[3]. 特点
- 非递归结构:Fiber 使用链表结构替代递归,便于任务中断和恢复。
- 每个虚拟 DOM 节点对应一个 Fiber 节点:Fiber 节点包含额外的状态信息(如副作用链表、优先级等)。
[4]. Fiber 节点结构示例
{
// 基本属性
type: "div",
props: { children: "Hello" },
// Fiber 特有属性
child: null, // 指向子 Fiber 节点
sibling: null, // 指向兄弟 Fiber 节点
return: parentFiber, // 指向父 Fiber 节点
stateNode: domNode, // 对应的真实 DOM 节点
pendingProps: {}, // 待更新的 props
memoizedProps: {}, // 上一次渲染的 props
updateQueue: null, // 更新队列
effectTag: "Placement", // 副作用标记(如插入、更新、删除)
nextEffect: null, // 指向下一个带副作用的 Fiber 节点
...
}
3.vdom 与 fiber的区别
| 特性 | 虚拟 DOM(VDOM) | Fiber |
|---|---|---|
| 本质 | 数据结构(描述 UI 的 JavaScript 对象树) | 算法(协调引擎,负责渲染调度) |
| 作用 | 快速构建 UI 快照,用于 Diff 算法 | 实现并发渲染、任务拆分、优先级调度 |
| 生命周期 | 每次组件渲染时生成 | 仅在 React 协调阶段初始化一次 |
| 是否可中断 | 不可中断(递归结构) | 可中断(链表结构) |
| 是否依赖关系 | Fiber 架构需要虚拟 DOM 作为输入 | 虚拟 DOM 独立于 Fiber 架构 |
4、vdome向fiber tree的转换过程
[1]. 深度优先遍历虚拟 DOM
-
React 以 深度优先 的方式遍历虚拟 DOM 树。
-
每个节点被转换为一个 Fiber 节点,并通过链表指针连接:
child:指向第一个子节点。sibling:指向下一个兄弟节点。return:指向上层父节点。
[2]. Fiber 节点的创建
-
函数式组件的Fiber 节点的创建:
- 创建
FunctionComponent类型的 Fiber 节点。 - 附加 Hook 信息(如
useState、useEffect的状态)。
- 创建
-
原生 DOM 节点的Fiber 节点的创建(如
<div>、<span>):- 创建
HostComponent类型的 Fiber 节点。 - 附加 DOM 相关属性(如
tag、type)。
- 创建
[3]. 链表结构的构建
-
虚拟 DOM 的递归结构被转换为 链表结构,便于中断和恢复。
-
示例:
虚拟 DOM 的嵌套结构:<div> <p>Hello</p> </div>对应的 Fiber Tree 链表结构:
{ type: "div", child: { type: "p", sibling: null, return: divFiber }, return: null }
[4]、Fiber Tree 的核心作用
-
支持可中断的渲染
- 链表结构允许 React 在遍历 Fiber Tree 时检查是否需要中断(如响应用户输入),并通过
shouldYield()判断。
- 链表结构允许 React 在遍历 Fiber Tree 时检查是否需要中断(如响应用户输入),并通过
-
优先级调度
- 每个 Fiber 节点可以分配不同的优先级(如动画 > 用户输入 > 渲染),高优先级任务可抢占低优先级任务。
-
副作用标记
- 在转换过程中,React 会标记 Fiber 节点的副作用(如
Placement插入、Update更新、Deletion删除),供提交阶段使用。 - 在更新过程中,通过diff算法,比较新旧两棵fiber tree,将对应节点标记副作用(如
Placement插入、Update更新、Deletion删除)。注:diff算法应用在新旧fiber树的比较,而不是新旧vdom的比较。
- 在转换过程中,React 会标记 Fiber 节点的副作用(如
四. commit 阶段
在 React 中,提交阶段(Commit Phase) 是渲染流程的最后一步,负责将 协调阶段(Reconciliation Phase) 标记的副作用(如插入、更新、删除 DOM 节点)应用到真实 DOM 上。
1、提交阶段的核心任务
[1]. 应用副作用(Effect Tags)
- 根据协调阶段标记的副作用(如 `Placement`、`Update`、`Deletion`),操作真实 DOM。
- **示例**:
- `Placement`:将新节点插入 DOM。
- `Update`:更新已有节点的属性(如 `textContent`、`className`)。
- `Deletion`:从 DOM 中移除旧节点。
[2]. 执行 useEffect 副作用
- 在 DOM 更新完成后,依次执行所有 `useEffect` 回调函数。
- **注意**:`useEffect` 的执行时机是 **提交阶段的最后一步**,确保副作用基于最新的 DOM 状态。
[3]. 处理 ref 的挂载与卸载
- 挂载 `ref`:当节点插入 DOM 时,调用 `ref.callback(currentValue)`。
- 卸载 `ref`:当节点从 DOM 移除时,调用 `ref.callback(null)`。
2、Commit 阶段的批量更新机制
函数式组件在 Commit 阶段的批量更新 是通过 副作用链表(Side Effect List) 和 DOM 操作的批量合并 实现的。React 会确保在 Commit 阶段一次性完成所有 DOM 操作和副作用的执行,从而减少浏览器的重排(Reflow)和重绘(Repaint),提高性能。
-
副作用链表(Side Effect List)
- 在 协调阶段(Reconciliation Phase) ,React 会为每个需要操作的 Fiber 节点标记副作用(如
Placement、Update、Deletion)。 - 这些节点通过
nextEffect指针串联成一个 副作用链表,React 在 Commit 阶段会按顺序遍历该链表,依次处理每个节点的副作用。
- 在 协调阶段(Reconciliation Phase) ,React 会为每个需要操作的 Fiber 节点标记副作用(如
-
DOM 操作的批量合并
-
React 会 批量执行所有 DOM 操作(如插入、更新、删除),而不是逐个节点操作。
-
示例:
jsx function App() { return ( <div> <p>Hello</p> <p>World</p> </div> ); }- 如果这两个
<p>节点都需要插入 DOM,React 会一次性将它们插入父节点,而不是分两次操作。
- 如果这两个
-
-
不可中断的提交
- Commit 阶段是 不可中断的,必须一次性完成所有 DOM 操作,以确保更新的原子性(Atomicity)。
- 这与 Reconciliation 阶段的可中断性形成对比。