Vue中v-for为什么不建议使用index作为key
本文深入解析Vue中v-for循环为何不建议使用index作为key。通过对比使用index与唯一ID时的虚拟DOM更新差异,揭示就地复用机制在数据排序或增删时导致的组件状态错乱与性能损耗问题,帮助开发者理解Diff算法原理并写出更高效的Vue代码。
前言
很多人可能会疑惑,在Vue中使用index作为key既直观又便捷,为何官方文档或最佳实践往往不建议这样做?本文将通过具体案例为您揭开其中的原因。
一、key的作用是什么?
key的作用众所周知,看一个demo就知道:
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>
<ul id="app">
<li v-for="(item,index) in list" :key="index">{{item}}</li>
</ul>
<script>
new Vue({
el: '#app',
data() {
return {
list: [1, 2, 3]
}
}
})
</script>
</body> 
在Vue中,key属性并非强制要求,即使不添加key,程序也能正常运行且不会报错。然而,从性能和维护角度考虑,强烈建议显式地添加key。
此外,必须明确key的应用场景,即配合v-for指令使用。key是Vue虚拟DOM中节点的唯一标识,借助它,Diff算法能够更精准、高效地比对节点。若不设置key,Vue采用的就地更新策略会复用现有DOM节点,导致旧状态残留,进而引发一系列难以排查的Bug。同时,key的唯一性使得Diff算法可以利用Map数据结构进行查找,将时间复杂度降低至O(1)。
简而言之,key作为唯一标识,旨在帮助Diff算法准确定位需要对比的节点。
二、什么是虚拟DOM?
虚拟DOM本质上是一个JavaScript对象,它描述了DOM树各层的结构特征。需要注意的是,在beforeCreated生命周期钩子执行时,真实的DOM树尚未生成。
单纯的概念阐述可能难以理解,我们通过代码来直观展示:
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>
<div id="app">
<ul>
<item v-for="(num,index) in list" :key="index":num="num":class=`item${num}`></item>
</ul>
<button @click="change">change</button>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
list: [1, 2, 3]
}
},
methods: {
change() {
this.list.reverse()
}
},
//也可以使用原生JS添加DOM结构,不考虑性能的时候
components: {
item: {
props: ['num'],
template: `<div>{{num}}</div>`,
name: 'child'
}
}
})
// let ul = document.getElementById('app');
// for (let i = 1; i <= 3; i++) {
// let li = document.createElement('li');
// li.innerHTML = i;
// ul.appendChild(li);
// }
</script>
</body>首先,系统会生成虚拟DOM结构,而非直接编译:
vnode = {
tag: 'ul',
children: [
{
tag: 'li',
children: [{
vnode: {
text: '1'
}
}]
},
{
tag: 'li',
children: [{
vnode: {
text: '2'
}
}]
},
{
tag: 'li',
children: [{
vnode: {
text: '3'
}
}]
},
]
}随后,再将HTML模板编译为真实的DOM结构:
再编译成真实DOM结构
<li>1</li>
<li>2</li>
<li>3</li>虚拟DOM存在的意义就是,减少真实DOM结构的操作,对于浏览器来说,减少开销,提高性能。
三、什么是diff算法?
只要数据源发生变化,就会触发Watcher观察者函数(Object.defineProperty)的回调,从而驱动视图更新(此处加下划线是为了表明这是Vue内部自带的方法)。
回调逻辑如下:
vm._update(vm._render()),
_render会生成vnode
_update会拿着vnoe去__patch__去查找不同
__patch__中启动的就是diff算法<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>
<div id="app">
<ul>
<item v-for="(item,index) in list" :key="item.id" :num="item.num" :class=`item${item.num}`></item>
</ul>
<button @click="change">change</button>
</div >
<script>
new Vue({
el: '#app',
data() {
return {
list: [1, 2, 3]
},
methods: {
change() {
this.list.reverse()
}
},
components: {
item: {
props: ['num'],
template: `<div>{{ num }}</div>`,
name: 'child'
}
}
})
</script>
</body >当点击按钮触发change事件后,list数据反转为[3, 2, 1]。此时,Diff算法会根据DOM树结构,从上到下、从左到右判断值是否更新。关键在于,Diff算法如何知道哪些节点应该相互比对?这正是key的作用所在,它作为唯一标识符,指导Diff算法进行精准比对。

四、为什么不能用index作为key?
基于上述知识基础,我们更能深入理解为何应避免使用index作为key。以下将通过三个Demo进行演示:
(1)index不能作为key--情景一
Demo1示例:
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>
<div id="app">
<ul>
<item v-for="(num,index) in list" :key="index" :num="num" :class=`item${num}`></item>
</ul>
<button @click="change">change</button>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
list: [1, 2, 3]
}
},
methods: {
change() {
this.list.reverse()
}
},
components: {
item: {
props: ['num'],
template: `<div>{{num}}</div>`,
name: 'child'
}
}
})
</script>
</body>Demo2示例:
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>
<div id="app">
<ul>
<item v-for="(item,index) in list" :key="item.id" :num="item.num" :class=`item${item.num}`></item>
</ul>
<button @click="change">change</button>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
list: [{
id: 0,
num: 1
}, {
id: 1,
num: 2
}, {
id: 2,
num: 3
}]
}
},
methods: {
change() {
this.list.reverse()
}
},
components: {
item: {
props: ['num'],
template: `<div>{{num}}</div>`,
name: 'child'
}
}
})
</script>
</body>Demo1与Demo2的核心区别在于key值的设定:Demo1使用index作为key,而Demo2使用list数组中对象的id作为key。需要特别注意的是,当点击change按钮后,请仔细观察代码层面的变化:
Demo1点击前后的DOM变化:


Demo2点击前后的DOM变化:


表面上看,Demo1仅交换了item1和item3的位置,点击后似乎只改变了这两个元素;而Demo2中三个元素都发生了改变。此时,有人可能会认为使用index作为key能减少DOM结构的改动,岂不是更高效?
实际上,眼见未必为实。我们来看看底层真实的情况:
Demo1改变前后的节点对比:

Demo2改变前后的节点对比:

结果令人震惊!在Demo2中,list实际的key与num一一对应,并未发生实质改变,仅是渲染顺序不同。而在Demo1中,却改变了两个节点的值。这就是不建议使用index作为key的原因之一。
使用index作为key会导致Diff算法中的复用优化失效(降低了复用性,违背了虚拟DOM的初衷)。原本可以复用的节点无法复用,徒增性能开销,造成资源浪费。
(2)index不能作为key---情景二
接下来查看Demo3,它将淋漓尽致地展现使用index作为key引发的Bug:
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>
<div id="app">
<ul>
<item v-for="(item,index) in student" :key="index"></item>
//<item v-for="(item,index) in list" :key="item.id"></item>
</ul>
<button @click="change">change</button>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
list: [{
id: 0,
num: 1
}, {
id: 1,
num: 2
}, {
id: 2,
num: 3
}],
student: [1, 2, 3]
}
},
methods: {
//删除数据
change() {
this.student.splice(0, 1)
//this.list.splice(0, 1)
}
},
components: {
item: {
template: `<div>{{Math.random()}}</div>`,
}
}
})
</script>
</body>当执行删除操作时,又会发生什么意想不到的事情呢?


右图展示了删除后的结果,令人惊讶的是,我们明明删除的是第一个数据,页面展示的却是删除了第三个数据。这确实令人困惑,但通过虚拟DOM的原理分析,便能恍然大悟。
当使用index作为key时,由于Diff算法采用就地更新策略,会复用key:0的节点。在比对过程中,发现key:0的节点存在,则继续比对key:1,同样存在,而多出的key:2则会被删除。

使用index作为key时,在删除数据的过程中,因为Vue不会深入比对子组件的文本内容,所以会错误地移除VDOM中的节点(index的值是不固定的)。
此例中,文本内容使用了随机数,因此数据对不上不必过于在意。另外,既然index的值不固定,不能作为key,那么扩展思考一下,是否可以使用随机数作为key呢?答案显然是否定的。因为无论是删除还是反转操作,根本找不到相同的key,Diff算法将失去意义,完全丧失了复用性。
在这个Demo3中,数据源包含了list数组,读者可以尝试遍历它,对比使用index作为key的结果,现象将会更加明显。
到此这篇关于在Vue中为什么不能用index作为key的文章就介绍到这了,更多相关Vue index key内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!
Vue.js 高阶组件封装与自定义 Hook 实战技巧
2026-11-05 17:15:22
怎么在Vue3项目中集成轻量级JS文本编辑器
2026-11-03 12:21:35
Vue.js官网怎么切换Vue 2与Vue 3版本文档?避免看错版本指南
2026-11-03 08:08:08
ElementUI el-switch 开关组件使用详解与高级应用
2026-10-31 15:21:18
Element UI 表格行按钮点击加载与状态管理实现
2026-10-31 14:12:32








