← 返回目录

第 23 卷

第 23 / 28 卷日期:

• 假设 1:同类型的组件产生相似的 DOM 结构,不同类型的组件产生不同的 DOM 结构:

• 若两个组件类型相同(如都是 <div> 或都是自定义组件 <UserList>),则认为它们的 DOM 结构相

似,继续深入对比其属性和子节点;

• 若两个组件类型不同,则直接销毁旧组件对应的 DOM 结构,创建新组件对应的 DOM 结构,无需

深入对比子节点(大幅减少对比成本)。

• 假设 2:列表节点通过唯一的 key 属性,可以确定节点在列表中的位置是否变化:

• 对于列表类子节点(如 <ul> 下的多个 <li>),若未设置 key,React 会通过“索引”对比,可能导

致错误的 DOM 复用;

• 若设置了唯一的 key,React 可通过 key 快速定位节点的新增、删除、移动操作,提升列表对比效

率(详细逻辑见第 35 题)。

2. Diff 算法的核心工作流程:分层对比(深度优先)

React Diff 算法采用「分层对比」策略,而非传统的“全量递归对比”,即从根节点开始,按层级遍历对比新旧虚拟 DOM 树的节点,同一层级的节点逐一对比,不跨层级对比(基于“UI 页面的 DOM 结构

很少跨层级变化”的现实情况,大幅减少对比成本)。

具体工作流程可分为三个核心步骤:

(1)对比根节点:根据组件类型判断是否复用 DOM

首先对比新旧虚拟 DOM 树的根节点,根据节点类型(type)分为两种情况:

• 节点类型不同(如旧节点是 <div>,新节点是 <p>):

  • 直接销毁旧节点对应的真实 DOM 及其所有子节点;
  • 创建新节点对应的真实 DOM 及其所有子节点,替换旧节点的位置;
  • 无需继续对比该节点的子节点(因为类型不同,DOM 结构完全不同)。

• 节点类型相同(如新旧节点都是 <div>):

  • 复用旧节点对应的真实 DOM(避免重新创建 DOM 的开销);

• 对比节点的属性(props):若属性有变化(如 className、id、事件绑定等),则更新真实

DOM 的对应属性;若属性无变化,则不操作;

  • 继续递归对比该节点的子节点(进入下一层级对比)。

(2)对比子节点:分“非列表子节点”和“列表子节点”优化

当父节点类型相同,需要对比其子节点时,React 会根据子节点的类型(是否为列表)采用不同的对比

• 非列表子节点(如 <div> 下的 <span>、<img> 等无序子节点):

  • 按“索引顺序”逐一对比新旧子节点列表中的节点;
  • 若旧子节点列表长度大于新列表:删除多余的旧子节点对应的 DOM;
  • 若新子节点列表长度大于旧列表:新增多余的新子节点对应的 DOM;
  • 若索引对应的节点类型相同:复用 DOM 并更新属性,继续对比其子节点;
  • 若索引对应的节点类型不同:销毁旧节点,创建新节点,替换位置。

• 列表子节点(如 <ul> 下的多个 <li>,或通过 map 生成的子节点):

  • 无 key 时:按索引对比(存在缺陷,如列表插入/删除元素时导致错误复用 DOM);

• 有 key 时:通过 key 建立新旧节点的映射关系,快速判断节点的“新增”“删除”“移动”操作:

  • 步骤 1:遍历新子节点列表,用 key 构建“key - 新节点”的映射表;

• 步骤 2:遍历旧子节点列表,通过 key 查找新节点列表中是否存在对应节点:

  • - 若不存在:标记该旧节点为“删除”,后续销毁对应的 DOM;
  • - 若存在:对比节点类型和属性,若有变化则更新,同时记录节点的位置变化;

• 步骤 3:遍历新子节点列表,查找旧节点列表中不存在的 key,标记为“新增”,后续创建对应的

DOM 并插入;

• 步骤 4:根据节点位置变化记录,调整真实 DOM 的顺序(避免销毁再创建,仅移动位置,提升效

率)。

(3)标记差异并提交更新

在对比过程中,React 会为所有需要更新的虚拟 DOM 节点标记“差异类型”(如属性更新、节点新增、节点删除、节点移动等);

对比完成后,React 会一次性将所有标记的差异同步到真实 DOM 中(批量更新),避免频繁操作真实 DOM 导致的多次重排/重绘。

3. 示例:简单 Diff 对比过程

假设旧虚拟 DOM 树:

Code block

1<div className="old"> 2<p>旧文本</p>

3<ul> 4<li key="1">列表项 1</li>

5<li key="2">列表项 2</li> 6</ul>

1<div className="new"> 2<p>新文本</p>

3<ul> 4<li key="1">列表项 1</li>

5<li key="3">列表项 3</li> 6</ul>

7</div>

Diff 对比过程:

  1. 对比根节点(都是 <div>):复用 DOM,更新 className 为 "new";
  2. 对比 <div> 的子节点(<p> 和 <ul>):
  3. - 对比 <p> 节点(类型相同):复用 DOM,更新文本内容为 "新文本";
  4. - 对比 <ul> 节点(类型相同):复用 DOM,继续对比其子节点;
  5. 对比 <ul> 的子节点(列表项):
  6. - 通过 key 对比:key="1" 的节点存在且无变化,复用;key="2" 的节点在新列表中不存在,标记

删除;key="3" 的节点在旧列表中不存在,标记新增;

  1. 提交更新:同步 className 变化、文本变化,删除 key="2" 的 <li>,新增 key="3" 的 <li>。

4. 关键总结

React Diff 算法的核心优势是「基于合理假设的分层对比策略」:通过“同类型组件深入对比、不同类型组件直接替换”减少对比深度,通过“key 优化列表对比”减少列表操作成本,最终实现高效的差异

查找;对比完成后批量更新真实 DOM,最小化 DOM 操作开销,提升应用性能。

三十五、React 在处理列表时为什么推荐使用唯一的 key 属性?这与 Diff

算法有什么关系?

1. 核心原因:帮助 Diff 算法精准识别列表节点的变化

React 处理列表时推荐使用唯一的 key 属性,核心目的是「为列表中的每个节点提供唯一标识」,帮助 Diff 算法快速、准确地判断节点的“新增”“删除”“移动”操作,避免错误的 DOM 复用,同时提

升列表对比的效率。

若不设置 key 或 key 不唯一,React 会默认使用「索引」作为 key 对比节点,这种方式在列表发生“插入”“删除”“重新排序”等操作时,会导致 Diff 算法误判,引发 UI 异常或性能问题。

全站分卷索引 · 28 卷
  1. 1第 1 卷
  2. 2第 2 卷
  3. 3第 3 卷
  4. 4第 4 卷
  5. 5第 5 卷
  6. 6第 6 卷
  7. 7第 7 卷
  8. 8第 8 卷
  9. 9第 9 卷
  10. 10第 10 卷
  11. 11第 11 卷
  12. 12第 12 卷
  13. 13第 13 卷
  14. 14第 14 卷
  15. 15第 15 卷
  16. 16第 16 卷
  17. 17第 17 卷
  18. 18第 18 卷
  19. 19第 19 卷
  20. 20第 20 卷
  21. 21第 21 卷
  22. 22第 22 卷
  23. 23第 23 卷
  24. 24第 24 卷
  25. 25第 25 卷
  26. 26第 26 卷
  27. 27第 27 卷
  28. 28第 28 卷