在使用
v-for时,我们经常看到需要添加:key属性。这个看似简单的属性实际上非常重要,理解它的工作原理可以帮助你避免很多奇怪的问题。
key 是 Vue 用来标识节点的一种机制。在虚拟 DOM 的 diff 算法中,key 帮助 Vue 识别哪些元素是相同的,哪些是不同的。
看一个例子:
<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>
没有 key 时,Vue 使用"就地更新"策略:
旧列表:A(输入1) B(输入2) C(输入3)
新列表:C B A
Vue 的处理:
- 第一个位置:A → C(更新文本,保留输入框)
- 第二个位置:B → B(不变)
- 第三个位置:C → A(更新文本,保留输入框)
Vue 认为元素没变,只是内容变了,所以保留了输入框的状态。
<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 的元素移动到第三个位置
现在输入框会跟随项目正确移动。
Vue 的 diff 算法在比较新旧节点时:
// 简化的判断逻辑
function isSameNode(oldVnode, newVnode) {
return oldVnode.key === newVnode.key &&
oldVnode.tag === newVnode.tag
}
<!-- ✅ 正确:使用唯一 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 可能导致:
<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:
<!-- 静态列表,可以用索引 -->
<ul>
<li v-for="(item, index) in staticItems" :key="index">
{{ item }}
</li>
</ul>
当 v-for 用于组件时,key 尤其重要:
<template v-for="todo in todos" :key="todo.id">
<todo-item :todo="todo"></todo-item>
</template>
没有 key,组件的状态可能会混乱;有 key,组件会正确复用或重建。
在 <transition> 中,key 用于强制替换元素:
<transition>
<span :key="text">{{ text }}</span>
</transition>
当 text 改变时,<span> 会被替换而非更新,从而触发过渡动画。
<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:可能删除错误,或状态混乱
<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 开发的最佳实践。