key 的重要性

在使用 v-for 时,我们经常看到需要添加 :key 属性。这个看似简单的属性实际上非常重要,理解它的工作原理可以帮助你避免很多奇怪的问题。

什么是 key

key 是 Vue 用来标识节点的一种机制。在虚拟 DOM 的 diff 算法中,key 帮助 Vue 识别哪些元素是相同的,哪些是不同的。

为什么需要 key

问题演示

看一个例子:

<div id="app">
  <div v-for="item in items">
    <input type="text">
    <span>{{ item.text }}</span>
  </div>
  <button @click="reverse">反转列表</button>
</div>

<script>
new Vue({
  el: '#app',
  data: {
    items: [
      { id: 1, text: 'A' },
      { id: 2, text: 'B' },
      { id: 3, text: 'C' }
    ]
  },
  methods: {
    reverse: function() {
      this.items.reverse()
    }
  }
})
</script>
  1. 在三个输入框中分别输入 "1"、"2"、"3"
  2. 点击"反转列表"按钮
  3. 列表顺序变为 C、B、A
  4. 问题:输入框中的值仍然是 "1"、"2"、"3",没有跟随项目移动

原因分析

没有 key 时,Vue 使用"就地更新"策略:

旧列表:A(输入1)  B(输入2)  C(输入3)
新列表:C         B         A

Vue 的处理:
- 第一个位置:A → C(更新文本,保留输入框)
- 第二个位置:B → B(不变)
- 第三个位置:C → A(更新文本,保留输入框)

Vue 认为元素没变,只是内容变了,所以保留了输入框的状态。

使用 key 解决

<div v-for="item in items" :key="item.id">
  <input type="text">
  <span>{{ item.text }}</span>
</div>

有了 key,Vue 可以正确识别元素:

旧列表:A(key=1)  B(key=2)  C(key=3)
新列表:C(key=3)  B(key=2)  A(key=1)

Vue 的处理:
- key=3 的元素移动到第一个位置
- key=2 的元素保持在第二个位置
- key=1 的元素移动到第三个位置

现在输入框会跟随项目正确移动。

key 的工作原理

Vue 的 diff 算法在比较新旧节点时:

  1. 有 key:根据 key 判断是否是同一节点
  2. 无 key:根据位置判断是否是同一节点
// 简化的判断逻辑
function isSameNode(oldVnode, newVnode) {
  return oldVnode.key === newVnode.key && 
         oldVnode.tag === newVnode.tag
}

key 的使用规则

使用唯一标识

<!-- ✅ 正确:使用唯一 id -->
<li v-for="item in items" :key="item.id">{{ item.name }}</li>

<!-- ❌ 错误:使用索引(可能导致问题) -->
<li v-for="(item, index) in items" :key="index">{{ item.name }}</li>

不要使用索引作为 key

使用索引作为 key 可能导致:

  1. 性能问题:列表变化时,大量不必要的更新
  2. 状态错乱:表单输入、选中状态等可能错位
<div id="app">
  <div v-for="(item, index) in items" :key="index">
    <input type="checkbox"> {{ item.text }}
  </div>
  <button @click="addItem">添加到开头</button>
</div>

<script>
new Vue({
  data: {
    items: [
      { id: 1, text: 'A' },
      { id: 2, text: 'B' }
    ]
  },
  methods: {
    addItem: function() {
      this.items.unshift({ id: 3, text: 'C' })
    }
  }
})
</script>

如果勾选了 A,添加新项后,勾选状态会出现在 C 上。

什么时候可以用索引

以下情况可以使用索引作为 key:

  1. 静态列表:列表不会变化
  2. 纯展示:没有表单元素或组件状态
  3. 不会重新排序:不会进行排序、插入、删除操作
<!-- 静态列表,可以用索引 -->
<ul>
  <li v-for="(item, index) in staticItems" :key="index">
    {{ item }}
  </li>
</ul>

key 与组件

v-for 用于组件时,key 尤其重要:

<template v-for="todo in todos" :key="todo.id">
  <todo-item :todo="todo"></todo-item>
</template>

没有 key,组件的状态可能会混乱;有 key,组件会正确复用或重建。

key 与 transition

<transition> 中,key 用于强制替换元素:

<transition>
  <span :key="text">{{ text }}</span>
</transition>

text 改变时,<span> 会被替换而非更新,从而触发过渡动画。

实际案例

案例 1:删除列表项

<div id="app">
  <div v-for="item in items" :key="item.id">
    {{ item.text }}
    <button @click="remove(item.id)">删除</button>
  </div>
</div>

<script>
new Vue({
  data: {
    items: [
      { id: 1, text: 'A' },
      { id: 2, text: 'B' },
      { id: 3, text: 'C' }
    ]
  },
  methods: {
    remove: function(id) {
      var index = this.items.findIndex(function(item) {
        return item.id === id
      })
      this.items.splice(index, 1)
    }
  }
})
</script>

有 key:删除正确,其他元素正确复用 无 key:可能删除错误,或状态混乱

案例 2:表格编辑

<table>
  <tr v-for="row in rows" :key="row.id">
    <td><input v-model="row.name"></td>
    <td><input v-model="row.age"></td>
  </tr>
</table>

有 key:编辑状态正确保存 无 key:排序后数据可能错位

小结

情况key 建议
列表会变化使用唯一 id
有表单元素必须使用唯一 id
纯展示静态列表可以用索引或不用 key
组件列表必须使用唯一 id

记住:始终为 v-for 提供唯一的 key,这是 Vue 开发的最佳实践。