跳到主要内容

协调算法

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 触发更新时:

  1. 生成新的 Virtual DOM 树
  2. 从根节点开始递归对比新旧树
    • 类型不同? → 销毁旧子树,创建新子树。
    • 类型相同? → 更新属性(props、style 等),并递归对比子节点。
  3. 处理子节点列表
    • 若有 key:建立“key → 节点”的映射,通过 key 快速匹配。
    • 若无 key:按索引顺序一对一比较。
  4. 收集差异(patches)
    • 生成最小操作集:INSERT, DELETE, UPDATE, MOVE
  5. 批量应用到真实 DOM

三、为什么 key 如此重要?

场景无 key / 用 index有唯一 key
插入/删除中间项后续所有项重渲染仅变更项更新
列表排序全部重渲染仅移动 DOM 节点
状态保持输入框、选中状态错乱状态正确绑定到对应项

✅ 正确使用 key 是避免性能问题和 UI bug 的关键!


四、补充:React 18 的改进

  • 自动批处理(Automatic Batching):多个 setState 合并为一次更新,减少 diff 次数
  • 选择性 hydration:SSR 时优先水合用户交互的组件,提升响应速度
  • 但核心 diff 算法逻辑未变

总结

原则说明
类型不同 → 重建子树避免频繁切换根元素类型
列表必须用 key确保高效更新和状态稳定
同层 diff,不跨层算法复杂度 O(n) 的基础