在解释绑定 key 值的作用前,我们先来看一个示例:
<div id="app"> <key-test v-for="(item, index) in list"> <button @click="remove(index)">删除{{item}}button> key-test> <button @click="add">添加button>div><script> Vue.component('key-test', { template: ', data: function () { return { value: Date.now() } } }) new Vue({ el: '#app', data: { list: [], index: 1 }, methods: { add: function () { this.list.push(this.index++) }, remove: function (index) { this.list.splice(index, 1) } } })script>
运行效果如下:
从运行效果中可以看到,当点击第一行删除按钮时,第一行的文本框内容并没有被删除,而是将最后一行中的文本框内容删除了,为什么会是这种情况呢?
这是因为用 v-for
更新已渲染过的元素列表时,它默认使用“就地复用”的策略,如果数据项的顺序发生了改变,Vue
不会移动 DOM
元素来匹配数据项的顺序,而是简单复用此处的元素。
来分析一下上述运行结果的原因:
当点击三次“添加”按钮时,页面中会渲染三次 组件内容,同时,父组件中的
list
数组元素值变为 [1, 2, 3]
。当点击第一行中的删除按钮时,调用 remove()
方法将 list
数组中的第一个元素删除,则 list
数组变为 [2, 3]
,然后视图中重新使用 v-for
渲染,按钮上的数据依赖于 list
中的元素值,所以被删除掉。由于之前的视图中已渲染过 组件三次,而
组件中的状态数据不受
list
数组中删除元素的影响,根据“就地复用”策略, 被就地复用。但数组中现在只有两个元素,也就是
只会被渲染二次,所以视图中的效果就是最后一行中的文本框数据被删除,第一行的按钮被删除。
这样的运行结果显示不合理,怎么解决这个问题呢,那就是添加 key
属性。引用官方文档的话:
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一
key
attribute
也就是说我们需要提供一个 key
属性,告诉 Vue
每个节点的差异,以便能够更好的跟踪节点信息。
<key-test v-for="(item, index) in list" :key="item"> <button @click="remove(index)">删除{{item}}button>key-test>
使用 v-for
实现列表渲染时,动态绑定 key
属性值,效果如下:
添加 key
后的效果就正常了,来分析一下原因。
这里使用 list
数组中的元素值作为 key
属性值,点击三次“添加”按钮后,list
数组值为 [1, 2, 3]
。点击第一行的“删除1”按钮,list
数组中将第一个元素删除,变为 [2, 3]
,重新渲染时,发现 key=2
与 key=3
的节点没有发生变化,而 key=1
的节点被删除了,所以视图中第一行的内容被删除了,这符合我们的预期。
为什么不用 index
作为 key
来使用呢?
如果使用 index
作为 key
:
<key-test v-for="(item, index) in list" :key="index"> <button @click="remove(index)">删除{{item}}button>key-test>
测试效果:
和没有添加 key
时的测试效果类似,这是为什么呢?再分析一下原因。
仍然先点击三次“添加”按钮,list
数组中元素为 [1, 2, 3]
,下标分别为 0, 1, 2
,点击第一行的删除按钮,list
数组元素变为 [2, 3]
,但数组中下标会自动重新编号,下标变为 0, 1
,利用 v-for
重新渲染时,使用 index
作为 key
值,所以 key=0
与 key=1
的元素还在,而 key=2
的元素被删除了,视图渲染的效果如上图所示,仍是最后一行文本框被删除,显然这又是不符合正常处理逻辑的。
因此,建议尽可能在使用 v-for
时为每项提供唯一的 key
属性,并且不使用 index
作为 key
值。