React 虚拟 DOM 与 Diff 算法:深度剖析与高效机制解析
1. 引言
React 作为现代前端的核心框架之一,能够在面对复杂 UI 变更时仍保持高性能,其关键在于:
- 虚拟 DOM (Virtual DOM)
- 高效的 Diff 算法(Reconciliation)
- Fiber 架构与异步调度
本文将从概念、实现、源码、流程图和实战代码五个维度深度剖析 React 的核心机制,帮助你真正理解为什么 React 能够高效渲染。
2. 虚拟 DOM 的概念与实现
2.1 什么是虚拟 DOM
虚拟 DOM 是 React 在内存中用 JS 对象表示真实 DOM 的抽象:
{
type: 'div',
props: { id: 'app', className: 'container' },
children: [
{ type: 'h1', props: null, children: ['Hello React'] },
{ type: 'p', props: null, children: ['Virtual DOM Demo'] }
]
}
每个虚拟 DOM 节点(VNode)可类比真实 DOM 的节点,但仅包含描述信息,不操作浏览器。
React 每次组件更新时,流程如下:
- 重新渲染组件 → 生成新的虚拟 DOM
- Diff 新旧虚拟 DOM → 找出最小差异
- Patch 真实 DOM → 最小化更新
2.2 虚拟 DOM 的优势
- 性能优化:减少直接 DOM 操作(浏览器 DOM 操作昂贵)
- 跨平台能力:同样机制可用于 React Native、SSR、WebGL
- 状态驱动渲染:开发者关注数据,React 负责高效 UI 更新
2.3 虚拟 DOM 渲染流程图
State / Props Change
|
v
+------------------------+
| Render Component |
+------------------------+
|
v
+------------------------+
| Generate Virtual DOM |
+------------------------+
|
v
+------------------------+
| Diff with Old VDOM |
+------------------------+
|
v
+------------------------+
| Patch Real DOM |
+------------------------+
3. React Diff 算法原理
React 的 Diff(协调)算法核心目标:
- 找出新旧虚拟 DOM 树的最小差异
- 将更新限制在最少的真实 DOM 操作
如果直接做树对比,复杂度是 O(n³),不可接受。
React 采用了 O(n) 的启发式策略:
- 同层对比,不跨层移动
- 不同类型节点直接销毁重建
- 列表节点用 key 做优化
3.1 三大 Diff 策略
- 类型不同 → 直接替换
// Old
<div>Hello</div>
// New
<span>Hello</span> // 整个 div 被卸载,span 被新建
- 同类型节点 → 属性 Diff + 子节点递归 Diff
// Old
<div className="a"></div>
// New
<div className="b"></div> // 只更新 className
- 列表节点 → key 识别移动
<ul>
{['A','B','C'].map(item => <li key={item}>{item}</li>)}
</ul>
正确使用 key 能让 React 复用节点,避免重建。
3.2 Diff 算法示意图
+------------------------------------+
| Compare New VDOM vs Old VDOM |
+------------------------------------+
|
v
Type Different? ---------> Replace Node
|
v
Props Different? --------> Update Props
|
v
Children Different? -----> Recurse Children Diff
3.3 简化版 Diff 代码示例
模拟实现一个简易的 Virtual DOM 和 Diff:
function createElement(type, props, ...children) {
return { type, props: props || {}, children };
}
function diff(oldVNode, newVNode) {
// 1. 类型不同 => 替换
if (!oldVNode || oldVNode.type !== newVNode.type) {
return { type: 'REPLACE', newVNode };
}
// 2. 属性对比
const propPatches = {};
const allProps = { ...oldVNode.props, ...newVNode.props };
for (let key in allProps) {
if (oldVNode.props[key] !== newVNode.props[key]) {
propPatches[key] = newVNode.props[key];
}
}
// 3. 子节点 Diff(递归)
const childPatches = [];
const maxLen = Math.max(oldVNode.children.length, newVNode.children.length);
for (let i = 0; i < maxLen; i++) {
childPatches.push(diff(oldVNode.children[i], newVNode.children[i]));
}
return { type: 'UPDATE', propPatches, childPatches };
}
React 内部 Diff 会结合 Fiber 架构进行任务切片,而不是同步递归完成。
4. Fiber 架构与异步 Diff
React 16 之后采用 Fiber 架构,核心目的是支持异步可中断渲染:
- Fiber 节点是虚拟 DOM 的链表化结构(单链表 + 指针)
- 渲染阶段可以被打断,保证主线程空闲时才更新 DOM
- 协调阶段 (Reconciliation) 计算 Diff
- 提交阶段 (Commit) 统一更新 DOM
4.1 Fiber 架构流程图
+------------------+
| Begin Work (Diff)|
+------------------+
|
v
+------------------+
| Reconcile Child |
+------------------+
|
v
+------------------+
| Complete Work |
+------------------+
|
v
+------------------+
| Commit to DOM |
+------------------+
4.2 Fiber 简化实现示例
模拟 Fiber 节点的数据结构:
class FiberNode {
constructor(vnode) {
this.type = vnode.type;
this.props = vnode.props;
this.child = null; // 第一个子 Fiber
this.sibling = null; // 下一个兄弟 Fiber
this.return = null; // 父 Fiber
this.stateNode = null; // 对应 DOM
}
}
// 构建 Fiber 树
function createFiberTree(vnode, parentFiber = null) {
const fiber = new FiberNode(vnode);
fiber.return = parentFiber;
if (vnode.children && vnode.children.length > 0) {
fiber.child = createFiberTree(vnode.children[0], fiber);
let current = fiber.child;
for (let i = 1; i < vnode.children.length; i++) {
current.sibling = createFiberTree(vnode.children[i], fiber);
current = current.sibling;
}
}
return fiber;
}
Fiber 的链表结构使得 React 可以在空闲时分片遍历,而非一口气完成全部递归。
5. 实战:Key 对 Diff 性能的影响
5.1 正确使用 key
function List({ items }) {
return (
<ul>
{items.map(item => <li key={item.id}>{item.text}</li>)}
</ul>
);
}
React 能通过 key
精确识别节点位置,复用已存在的 <li>
。
5.2 错误示例:使用索引作为 key
<ul>
{items.map((item, index) => <li key={index}>{item.text}</li>)}
</ul>
如果列表发生中间插入/删除,所有后续 DOM 会被误判为变化,引发不必要的重绘。
5.3 实际性能对比
function App() {
const [items, setItems] = React.useState(['A', 'B', 'C']);
function insert() {
setItems(prev => ['X', ...prev]);
}
return (
<>
<button onClick={insert}>Insert</button>
<List items={items} /> // 使用正确 key
</>
);
}
6. 总结
React 的高性能渲染来自三大核心机制:
- 虚拟 DOM:通过内存中计算差异,避免直接操作真实 DOM
- Diff 算法:O(n) 启发式对比,最小化更新
- Fiber 架构:支持异步可中断渲染,保证流畅度
评论已关闭