在 React 中,渲染一个包含子组件的组件涉及一系列底层流程,包括构建虚拟 DOM(React Element)、协调(Reconciliation)、Fiber 树管理和最终的 DOM 操作。以下是一个从底层解析的详细流程:
1. 初始化阶段
当开发者调用 React 的顶层 API 渲染组件时,例如:
jsx
ReactDOM.createRoot(document.getElementById('root')).render(<Parent />);
React 开始构建组件树。假设 Parent
组件包含子组件:
jsx
function Parent() {
return (
<div>
<Child />
</div>
);
}
function Child() {
return <p>Hello, World!</p>;
}
2. 创建 React Element
当调用 <Parent />
时,React 会将其转化为React 元素树(虚拟 DOM 树)。这一步是通过调用组件函数实现的:
-
React 会调用
Parent
,执行其返回值。 -
Parent
返回的是一个 JSX 树,它被转化为一个 JavaScript 对象,称为 React Element :javascriptconst parentElement = { type: 'div', props: { children: { type: Child, // 子组件 props: {}, // 子组件的 props }, }, };
-
对于
Child
,React 会继续递归调用它,得到:javascriptconst childElement = { type: 'p', props: { children: 'Hello, World!', }, };
3. 进入协调(Reconciliation)阶段
React 开始协调组件树,即比较新旧虚拟 DOM,决定需要的更新。
构建 Fiber 节点
React 为每个 React Element 创建一个对应的 Fiber 节点。Fiber 是 React 的内部数据结构,用于描述组件树的状态:
-
父组件
Parent
Fiber:javascriptconst parentFiber = { tag: 'HostComponent', // 表示是原生 DOM 元素 type: 'div', // 节点类型 stateNode: null, // 对应的真实 DOM(稍后会赋值) child: childFiber, // 指向子组件的 Fiber sibling: null, // 指向兄弟节点 };
-
子组件
Child
Fiber:javascriptconst childFiber = { tag: 'HostComponent', // 原生 DOM 元素 type: 'p', // 节点类型 stateNode: null, // 对应的真实 DOM child: null, // 没有子节点 sibling: null, // 没有兄弟节点 };
协调子节点
React 调用 reconcileChildren
比较新旧 Fiber 树的差异,决定是否复用、删除或新增节点。
- 在首次渲染时,旧 Fiber 树为空,React 会根据 React 元素树直接构建 Fiber 树。
- 对于每个 Fiber 节点,React 会递归地调用
beginWork
和completeWork
,处理子组件并更新其状态。
4. 渲染阶段(Render Phase)
beginWork
函数
beginWork
是 Fiber 协调的核心函数。它处理当前 Fiber 节点,并根据组件类型执行不同逻辑:
-
对于
Parent
(<div>
):- 检查是否需要更新:首次渲染会直接创建。
- 调用
reconcileChildren
处理子节点,递归进入Child
节点。
-
对于
Child
(<p>
):- 继续调用
reconcileChildren
,但因为它是文本节点,直接处理为完成状态。
- 继续调用
创建 DOM 节点
当 Fiber 节点处理完毕后,React 会通过 completeWork
创建真实的 DOM 节点,并赋值给 stateNode
:
-
对于
Parent
:javascriptparentFiber.stateNode = document.createElement('div');
-
对于
Child
:javascriptchildFiber.stateNode = document.createElement('p'); childFiber.stateNode.textContent = 'Hello, World!';
5. 提交阶段(Commit Phase)
提交阶段是 Fiber 树计算完成后,将更新应用到真实 DOM 的过程。
执行 DOM 操作
React 将根据 Fiber 树生成的 stateNode
,完成以下 DOM 操作:
- 将
Parent
的stateNode
(<div>
)挂载到根 DOM 容器。 - 将
Child
的stateNode
(<p>
)作为子节点,插入到Parent
的stateNode
中。
生命周期回调
对于有生命周期方法(如类组件)的组件,会在此阶段触发 componentDidMount
或 useEffect
回调。
6. 最终 DOM 输出
最终,React 在真实 DOM 中渲染出:
html
<div>
<p>Hello, World!</p>
</div>
7. 优化机制
React 的底层设计支持高效渲染:
- Fiber 数据结构:实现任务切片、可中断更新。
key
属性 :通过key
帮助 React 精确复用子节点,减少无意义的 DOM 操作。- 批量更新:React 会在批量更新模式下合并多次更新,减少重绘。
8. 总结
- React 将 JSX 转化为 React 元素树。
- React 构建 Fiber 树,递归调用组件以处理子组件。
- 在协调阶段,React 比较新旧 Fiber 树,生成新的 Fiber 树。
- 在渲染阶段,React 创建真实 DOM 并在提交阶段更新 DOM。
- 通过优化机制(如任务切片、批量更新、
key
比较等)提升性能。
React 的设计通过 Fiber 架构实现了高效渲染和可中断任务调度,使得复杂组件树的渲染和更新性能得到极大提升。
在 React 中,Fiber 架构通过 双缓存机制(Double Buffering) 实现了高效的更新和渲染。双缓存是 React Fiber 系统的核心设计之一,它使得 React 可以在后台异步构建新 Fiber 树,同时保持当前界面的稳定性。
以下是详细的解析和 Fiber 树的构建过程。
一、双缓存机制的简介
1. 双缓存的核心思想
React 使用两棵 Fiber 树:
- current Fiber 树:当前屏幕上显示的 Fiber 树,表示已渲染到 DOM 的 UI 状态。
- workInProgress Fiber 树 :新的一棵 Fiber 树,用于处理更新。当更新完成后,这棵树会替代
current
成为新的current
。
这两棵树通过 alternate
属性互相连接:
javascript
workInProgress.alternate === current; // 双向连接
current.alternate === workInProgress;
2. 双缓存的优点
- 异步更新 :React 可以在后台安全地构建
workInProgress
,不影响当前界面。 - 性能优化 :仅在
workInProgress
完成后切换为current
,最大限度减少渲染期间的 DOM 操作。 - 错误恢复 :在更新失败时,可以直接回退到
current
树,不影响用户体验。
二、Fiber 树的结构
每个 Fiber 节点代表一个 React 元素(如组件、DOM 节点、文本节点等),其结构如下:
javascript
const fiberNode = {
type, // 节点类型,例如 'div' 或组件函数
tag, // 节点标签,表示是 DOM 节点、函数组件还是类组件
key, // 用于列表比较的唯一标识
stateNode, // 真实 DOM 节点或类组件实例
child, // 第一个子 Fiber 节点
sibling, // 下一个兄弟 Fiber 节点
return, // 父 Fiber 节点
alternate, // 指向当前树中的对应 Fiber 节点
effectTag, // 表示需要对节点执行的操作类型(如插入、删除、更新)
updateQueue, // 存储该节点的更新队列
};
三、Fiber 树的构建过程
1. 初始化阶段
当 React 首次渲染时,React 会根据 React.createElement
的返回值生成初始 Fiber 树(即 current
树)。
步骤:
-
创建根 Fiber 节点 :React 为
ReactDOM.createRoot
创建一个rootFiber
,作为根节点。javascriptconst rootFiber = { tag: 'HostRoot', // 根 Fiber 的标签 stateNode: container, // 指向挂载的 DOM 容器 child: null, // 根 Fiber 的子节点 };
-
递归生成 Fiber 树:
- 对根组件调用
beginWork
。 - 根据返回的 React 元素,递归生成子 Fiber 节点并连接父子关系。
- 对根组件调用
2. 更新阶段(使用双缓存)
当组件状态或属性发生变化时,React 会在 workInProgress
树上进行更新操作。
步骤解析:
(1) 创建 workInProgress
树
-
React 会复用
current
树中的大部分 Fiber 节点,通过alternate
连接生成workInProgress
树:javascriptconst workInProgress = current.alternate || createNewFiber(current);
-
如果存在
current.alternate
,表示已存在workInProgress
,React 会复用它;否则,创建一个新的 Fiber 节点。
(2) 调用 beginWork
构建 Fiber 树
React 的协调逻辑从根节点开始,递归调用 beginWork
构建或更新每个 Fiber 节点。
beginWork
的核心任务:
-
检查更新类型:
- 如果节点没有变化,复用
current
。 - 如果有变化,创建新的 Fiber 节点。
- 如果节点没有变化,复用
-
调用子组件或函数组件的
render
方法,生成新的子 React 元素。 -
调用
reconcileChildren
比较子节点,生成或复用子 Fiber 节点,并连接到当前 Fiber 节点。
示例:
对于以下 JSX:
jsx
function App() {
return (
<div>
<p>Hello, World!</p>
</div>
);
}
beginWork
的流程:
- 对
<div>
节点创建 Fiber。 - 调用
reconcileChildren
处理子节点<p>
。 - 对
<p>
节点创建或复用 Fiber。
(3) 完成 completeWork
阶段
当 Fiber 树的所有节点完成构建后,React 进入 completeWork
阶段:
- 为每个 Fiber 节点生成对应的真实 DOM 节点(如果是原生节点)。
- 将子节点的 DOM 节点挂载到父节点的
stateNode
上。
3. 提交阶段
当 workInProgress
树构建完成后,React 会进入提交阶段(Commit Phase):
切换 Fiber 树
-
将
workInProgress
树设置为新的current
树:javascriptroot.current = workInProgress;
执行 DOM 操作
- 根据 Fiber 节点的
effectTag
,执行插入、删除或更新 DOM 的操作。
四、Fiber 树构建的性能优化
-
优先级调度:
- React 根据任务优先级(如用户交互任务 vs 数据更新任务)灵活地分片处理 Fiber 节点。
- 高优先级任务(如用户输入)可打断低优先级任务。
-
复用机制:
- Fiber 节点通过
alternate
实现复用,避免重复创建。 - 对于未变更的节点,直接复用。
- Fiber 节点通过
-
Key 对比:
- 在
reconcileChildren
中,通过key
提升子节点的匹配效率,减少不必要的节点销毁和重建。
- 在
-
最小化 DOM 操作:
- React 在
workInProgress
树中完成所有计算,仅在提交阶段操作 DOM。
- React 在
五、总结
-
React 的 Fiber 树通过双缓存机制实现:
current
树:表示当前屏幕显示的状态。workInProgress
树:在后台异步构建的新状态树。- 它们通过
alternate
属性连接,更新完成后切换树的角色。
-
构建 Fiber 树的过程包括:
beginWork
阶段:生成或复用 Fiber 节点,递归处理子节点。completeWork
阶段:完成 DOM 节点创建并挂载子 DOM。- 提交阶段:执行 DOM 操作并切换 Fiber 树。
-
Fiber 的设计提升了 React 的性能,使其能高效处理复杂更新任务和交互。