协调算法
React 的 Diff 算法(也称为 Reconciliation 协调算法)是 React 高性能的核心机制之一。它的目标是:在更新 UI 时,用最少的 DOM 操作将旧的 Virtual DOM 树转换为新的 Virtual DOM 树。
一、React Diff 的三大策略(核心假设)
1. 不同类型的元素,生成不同的树
- 如果根节点类型不同(如
<div>→<span>),React 会销毁整棵旧子树,重新创建新子树。 - 类组件 vs 函数组件也被视为不同类型。
// 从 div 变成 p → 整个子树重建
<div><Child /></div> → <p><Child /></p>
💡 优化建议:避免频繁切换根元素类型。
2. 通过 key 属性识别列表中的稳定元素
- 对于列表(同级多个子节点),React 默认按索引顺序对比。
- 但若提供 唯一且稳定的
key,React 能精准识别哪些项是新增、删除或移动的。
有唯一 key:
// key="A", key="B", key="C"
[ A, B, C ] → [ A, C ]
// React 知道:B 被删,C 位置不变 → 仅删除 B
📌 关键:
key必须是唯一、稳定、与业务相关的 ID(如数据库 ID),不要用数组索引(除非列表静态不变)。
3. 同一层级的子节点,按顺序逐个对比
- React 不会跨层级复用节点(即不会把深层子节点提升到上层复用)。
- 它只在同一父节点下的直接子节点列表中进行 diff。
// React 只对比 ul 的直接子节点(li),不会深入 li 内部再和其他 ul 的 li 比较
<ul>
<li key="1">A</li>
<li key="2">B</li>
</ul>
💡 这保证了算法复杂度为 O(n),而非 O(n³)。
二、Diff 的具体过程(简化版)
当 setState 触发更新时:
- 生成新的 Virtual DOM 树
- 从根节点开始递归对比新旧树:
- 类型不同? → 销毁旧子树,创建新子树。
- 类型相同? → 更新属性(props、style 等),并递归对比子节点。
- 处理子节点列表:
- 若有
key:建立“key → 节点”的映射,通过 key 快速匹配。 - 若无
key:按索引顺序一对一比较。
- 若有
- 收集差异(patches):
- 生成最小操作集:
INSERT,DELETE,UPDATE,MOVE
- 生成最小操作集:
- 批量应用到真实 DOM
三、为什么 key 如此重要?
| 场景 | 无 key / 用 index | 有唯一 key |
|---|---|---|
| 插入/删除中间项 | 后续所有项重渲染 | 仅变更项更新 |
| 列表排序 | 全部重渲染 | 仅移动 DOM 节点 |
| 状态保持 | 输入框、选中状态错乱 | 状态正确绑定到对应项 |
✅ 正确使用
key是避免性能问题和 UI bug 的关键!
四、补充:React 18 的改进
- 自动批处理(Automatic Batching):多个 setState 合并为一次更新,减少 diff 次数
- 选择性 hydration:SSR 时优先水合用户交互的组件,提升响应速度
- 但核心 diff 算法逻辑未变
总结
| 原则 | 说明 |
|---|---|
| 类型不同 → 重建子树 | 避免频繁切换根元素类型 |
| 列表必须用 key | 确保高效更新和状态稳定 |
| 同层 diff,不跨层 | 算法复杂度 O(n) 的基础 |