技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTVUE

快速、简洁讲明 Vue 中 v-for 循环 key 的作用

所属馆藏
Vue
文件格式
Markdown
原始路径
Vue/77-快速、简洁讲明Vue中v-for循环key的作用
本文目录7 个章节
  1. 一、key 到底解决什么问题
  2. 二、为什么通常不推荐使用 index 作为 key
  3. 三、key 和 Virtual DOM Diff
  4. 四、key 的使用规则
  5. 五、Vue 与 React 的类比
  6. 六、总结
  7. 参考资料

快速、简洁讲明 Vue 中 v-for 循环 key 的作用

原文通过列表插入、数组 index 和 Diff 图示说明 key。本文保留这些例子,并修正“有 key 就只渲染新增项”“没有 key 就一定很慢”等绝对化说法。

一、key 到底解决什么问题

key 的核心作用不是一个单纯的性能开关,而是告诉 Vue:

新旧列表中的哪些 VNode 代表同一个业务实体。

<li v-for="item in items" :key="item.id">
  {{ item.name }}
</li>

当列表发生插入、删除或排序时,Vue 可以通过稳定的 key 找到旧节点对应的新位置,从而复用并移动合适的 DOM 或组件实例。

如果没有 key,Vue 默认采用就地 patch策略:按当前位置复用节点,并把该位置更新成新数据。这种方式对简单、无状态的文本列表很高效;但如果子节点包含输入框、组件内部状态、焦点或过渡状态,就可能复用错节点对应的临时状态。

key 并不保证:

  • 只有新增项会创建 DOM;
  • 组件完全不会重新渲染;
  • 每次更新只产生一次 DOM 操作;
  • 所有场景都比无 key 更快。

它首先表达的是节点身份,性能收益是正确复用和移动带来的结果。

二、为什么通常不推荐使用 index 作为 key

下面是一个列表:

const list = [
  { id: 1, num: 1 },
  { id: 2, num: 2 },
  { id: 3, num: 3 },
]
<div v-for="(item, index) in list" :key="index">
  {{ item.num }}
</div>

1. 在末尾追加

如果只在末尾追加,并且列表不会排序、插入或删除中间项目:

const nextList = [
  { id: 1, num: 1 },
  { id: 2, num: 2 },
  { id: 3, num: 3 },
  { id: 4, num: '新增加的数据 4' },
]

原有 index 0、1、2 仍然对应原有实体,index 3 是新节点。在这种受限场景中使用 index 通常不会产生明显问题。

2. 在中间插入

如果在第二项前插入新数据:

const nextList = [
  { id: 1, num: 1 },
  { id: 4, num: '新增加的数据 4' },
  { id: 2, num: 2 },
  { id: 3, num: 3 },
]

使用 index 时,旧 key 和新 key 的对应关系是:

旧 key 0 → 新 key 0:仍是 id 1
旧 key 1 → 新 key 1:旧 id 2 被复用为新 id 4
旧 key 2 → 新 key 2:旧 id 3 被复用为新 id 2
旧 key 3 → 新 key 3:新建 id 3

Vue 并不会因为 index 变化就简单地“重新渲染三条完整 DOM”。更准确地说,它会按 key 复用当前位置的节点,再更新节点内容;如果节点是组件或带有输入状态,旧实体的本地状态可能被错误地保留在新实体上。

例如:

<input
  v-for="item in list"
  :key="index"
  v-model="item.name"
>

当列表前面插入项目时,输入框 DOM 可能按位置复用,焦点、未提交输入或子组件状态就不一定跟随业务实体移动。

3. 使用稳定的业务 ID

<li v-for="item in list" :key="item.id">
  {{ item.num }}
</li>

新旧列表的身份匹配是:

旧 id 1 → 新 id 1:复用并保持位置
旧 id 2 → 新 id 2:复用并移动到新位置
旧 id 3 → 新 id 3:复用并移动到新位置
新 id 4:创建新节点

这更符合业务实体的身份,也更适合组件列表、可编辑表单、拖拽排序和过渡动画。

三、key 和 Virtual DOM Diff

当数据变化时,Vue 会重新生成相关组件的 VNode,并由 renderer 比较新旧 VNode。简化理解:

  1. 主要进行同层比较;
  2. 节点类型不同,通常卸载旧节点并创建新节点;
  3. 节点类型相同且 key 相同,才有机会复用;
  4. keyed children 可以通过 key 建立旧节点到新位置的映射;
  5. Vue 2 使用双端比较等策略,Vue 3 对 keyed children 还会使用最长递增子序列优化移动。

真实 DOM 和虚拟 DOM 的更新关系(已本地化)

没有 key 的就地 patch

没有 key 时,Vue 通常按位置复用:

旧:A  B  C
新:A  F  B  C

位置 0:A 复用
位置 1:旧 B 更新成 F
位置 2:旧 C 更新成 B
位置 3:创建 C

对于只有文本的列表,这可能是合理且高效的;对于有状态的子树,则要使用稳定 key 表达身份。

使用 key 的身份匹配

旧:A  B  C
新:A  F  B  C

key A:复用
key B:移动
key C:移动
key F:创建

列表插入时的就地复用示意图(已本地化)

使用稳定 key 后的节点匹配示意图(已本地化)

Vue 可能通过移动已有 DOM 来完成结果,也可能因为属性、组件类型、过渡或编译标记而执行额外更新。图示用于说明身份匹配,不代表内部每一步的精确源码流程。

四、key 的使用规则

1. 使用稳定、唯一且原始类型的值

<div v-for="item in items" :key="item.id" />

key 应在同一列表范围内唯一、稳定,通常使用字符串或数字。不要使用:

<!-- ❌ 每次渲染都变化,导致节点不断重建 -->
<div v-for="item in items" :key="Math.random()" />

<!-- ❌ 对象 key 不是推荐的稳定原始值 -->
<div v-for="item in items" :key="item" />

重复 key 会导致匹配不确定和运行时警告。

2. template v-for 的 key

Vue 3 中使用 <template v-for> 渲染多个兄弟节点时,可以把 key 放在 template 上:

<template v-for="item in items" :key="item.id">
  <li>{{ item.name }}</li>
  <li class="divider" role="presentation" />
</template>

3. key 可以主动触发重建

改变组件 key 会让 Vue 把它视为新的组件实例:

<Editor :key="documentId" :document="document" />

这可以用于切换文档时重置编辑器内部状态,但不能把它当作普通的刷新按钮。频繁改变 key 会丢失组件状态并增加创建/销毁成本。

4. 不是所有列表都必须强行添加 key

Vue 官方建议在可能的情况下为 v-for 提供 key,尤其是:

  • 列表包含组件;
  • 列表包含表单输入或其他 DOM 临时状态;
  • 列表会排序、插入、删除;
  • 列表参与 Transition/TransitionGroup。

如果只是简单的无状态文本列表,并且明确希望使用就地 patch,不加 key 也可以是合理选择。规范要求和团队代码风格仍应保持一致。

五、Vue 与 React 的类比

React 的 map 列表同样需要 key 来表达元素身份,Vue 的 v-for 也是类似思想。但两者的具体 reconciliation 实现、编译优化和 API 不完全相同,不能因为都有 key 就认为 Diff 过程完全一致。

六、总结

  1. key 的第一职责是标识列表节点身份;
  2. 没有 key 时,Vue 默认按位置就地 patch;
  3. 稳定 key 允许 Vue 在列表插入、删除、排序时复用和移动正确节点;
  4. index 只适合不会重排、插入、删除且没有内部状态的受限列表;
  5. key 不等于“只有一项会渲染”,也不保证所有场景都更快;
  6. 使用业务实体 ID,避免随机值和重复 key;
  7. 修改 key 会主动销毁旧实例并创建新实例。

参考资料

原文作者:橙某人。原文关于 index、业务 ID、Virtual DOM 和 Diff 的主线予以保留;错误的 YAML 代码块、格式化残片、无关图片、重复推广链接以及“key 只让新增项渲染”等错误表述已清理或修正。

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS