← 返回目录

第 24 卷

第 24 / 28 卷日期:

(1)问题 1:UI 展示异常(错误复用带状态的节点)

若列表节点是带状态的组件(如输入框、复选框),使用索引作为 key 会导致 React 错误复用旧节点的 DOM,从而保留旧节点的状态,引发 UI 异常。

示例:

Code block

1// 初始列表(索引 0、1 作为 key)

2<ul>

3<li key={0}><input placeholder="输入内容" />列表项1</li>
4<li key={1}><input placeholder="输入内容" />列表项2</li>
5</ul>

若在列表头部插入一个新节点,新列表变为:

Code block

1<ul>

2<li key={0}><input placeholder="输入内容" />新列表项</li>
3<li key={1}><input placeholder="输入内容" />列表项1</li>
4<li key={2}><input placeholder="输入内容" />列表项2</li>
5</ul>

Diff 算法对比过程:

• 索引 0:旧节点是“列表项 1”,新节点是“新列表项”,类型相同,React 会复用旧节点的 DOM

(包括输入框的状态),仅更新文本内容;

  • 索引 1:旧节点是“列表项 2”,新节点是“列表项 1”,同样复用旧节点的 DOM;
  • 索引 2:新节点,创建新 DOM;

• 结果:若原列表项 1 的输入框有内容,插入新节点后,新列表项的输入框会保留原列表项 1 的内

容,出现 UI 异常。

(2)问题 2:性能损耗(不必要的 DOM 销毁与创建)

当列表发生重新排序时,使用索引作为 key 会导致 Diff 算法误判节点为“删除+新增”,而非“移动”,从而销毁旧节点并创建新节点,产生不必要的性能开销。

示例:列表 [A, B, C](索引 0、1、2)重新排序为 [B, A, C],使用索引作为 key 时:

• Diff 算法认为索引 0 的节点从 A 变为 B(销毁 A,创建 B),索引 1 的节点从 B 变为 A(销毁 B,

  • 实际只需移动 A 和 B 的位置即可,但因索引 key 导致额外的 DOM 销毁与创建,浪费性能。

当列表节点设置唯一的 key(如后端返回的 id、唯一标识符)时,Diff 算法会通过 key 建立新旧节点的映射关系,精准判断节点的变化类型,解决上述问题:

(1)优势 1:避免错误复用带状态节点

key 是节点的唯一标识,与索引无关,即使列表结构变化,React 也能通过 key 找到对应的节点,不会错误复用其他节点的 DOM 状态。

延续上述插入示例,设置 key 为节点唯一 id:

Code block

1// 初始列表(key 为 101、102)

2<ul>

3<li key={101}><input placeholder="输入内容" />列表项1</li>
4<li key={102}><input placeholder="输入内容" />列表项2</li>
5</ul>

6 7// 插入新节点后(新节点 key 为 100)

8<ul>

9<li key={100}><input placeholder="输入内容" />新列表项</li>
10<li key={101}><input placeholder="输入内容" />列表项1</li>
11<li key={102}><input placeholder="输入内容" />列表项2</li>
12</ul>

Diff 算法对比过程:

  • key=100:新节点,创建新 DOM(输入框为空,无状态复用);
  • key=101、102:旧节点存在,复用 DOM(保留原输入框状态);
  • 结果:UI 状态正常,无异常复用。

(2)优势 2:提升列表更新性能(识别节点移动)

通过 key 映射,Diff 算法能快速判断节点是否仅发生“位置移动”,无需销毁再创建 DOM,只需调整 DOM 顺序,大幅提升性能。

延续排序示例,列表 [A(key=101), B(key=102), C(key=103)] 重新排序为 [B(key=102), A(key=101), C(key=103)]:

  • Diff 算法通过 key 发现:B、A、C 都是旧节点,仅位置变化;
  • 直接移动 A 和 B 的 DOM 位置,无需销毁创建,性能开销极低。

• key 必须唯一:同一列表中不能有重复的 key,否则 React 会报警告,且 Diff 算法无法正确匹配节

• key 必须稳定:key 不应随渲染次数变化(如不推荐用 Math.random() 生成 key),否则每次渲染

都会被认为是新节点,销毁旧节点创建新节点,严重影响性能;

  • 优先使用后端返回的唯一 id:如数据库主键 id,确保 key 的唯一性与稳定性;

• 避免用索引作为 key:仅当列表是“静态列表”(无插入、删除、排序操作)且无状态时,可临时

使用,否则必须用唯一 id。

5. 与 Diff 算法的关系总结

key 是 React Diff 算法处理列表节点的「核心优化手段」:Diff 算法的设计基于“通过 key 确定节点位置变化”的假设,key 为 Diff 算法提供了节点的唯一标识,使其能:

  • 精准区分节点的“新增”“删除”“移动”操作,避免错误复用 DOM 导致的 UI 异常;
  • 减少不必要的 DOM 销毁与创建,通过移动 DOM 位置提升列表更新性能;
  • 降低 Diff 算法对比列表节点的复杂度,提升整体对比效率。

因此,使用唯一的 key 属性,是确保 React 列表渲染正确且高效的关键前提。

三十六、React 与 Vue 的 diff 算法在实现上有什么区别?

React 和 Vue 的 diff 算法核心目标一致:均为「高效对比新旧虚拟 DOM 树差异,最小化真实 DOM 操作」,且都基于“分层对比”(不跨层级对比,降低复杂度)的核心策略。但两者在具体实现细节、

优化方向、适用场景适配等方面存在显著差异,核心区别集中在“对比粒度”“列表优化细节”“静态节点处理”等维度。

1. 核心设计思路差异:“整体协调” vs “精准靶向”

• React Diff 算法:

• 核心思路是「整体协调(Reconciliation)」,更侧重“任务调度的兼容性”。由于 React 引入了

Fiber 架构支持可中断渲染,其 diff 算法需要适配优先级调度机制,因此对比过程被设计为“可中断的链表遍历”,而非一次性完成全量对比。

• 设计前提是「组件类型决定 DOM 结构相似性」:若组件类型不同,直接销毁旧组件 DOM 树并重

建新组件 DOM 树,不深入子节点对比;若类型相同,才继续对比属性和子节点。这种“粗粒度预判”能快速减少对比范围,适配 Fiber 的中断调度需求。

• Vue Diff 算法:

• 核心思路是「精准靶向更新」,更侧重“DOM 操作的最小化”。Vue 没有 Fiber 那样的可中断调度

机制(Vue3 虽引入调度器,但核心仍为同步对比),因此 diff 算法追求“一次性精准定位差异”,减少不必要的遍历和判断。

全站分卷索引 · 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 卷