vue中key使用的问题
前言
在vue要求在遍历的时候最好加上key,在使用过程中总有些疑问,在这里做下分析
1.不使用key的时候vue是怎么处理的
在vue2.x文档中有如下描述
key 的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。
在这段话中提到不使用key的时候,会尽量原地复用,复用的判断依据是常常在面试中被问到的sameVnode
这个方法:
function sameVnode (a, b) {
return (
// 因为没有定义key,可以得知a.key和b.key都是undefined,进而后面的条件进行判断;
a.key === b.key && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
) || (
isTrue(a.isAsyncPlaceholder) &&
a.asyncFactory === b.asyncFactory &&
isUndef(b.asyncFactory.error)
)
)
)
}
2.使用key一定能提高diff效率么
答案是并不是,可以看下面的例子
<script src="https://unpkg.com/vue@2.6.14/dist/vue.min.js"></script>
<div id="app">
<div class="ttt" v-for="(item) in list">{{item}}</div>
<button @click="changeData">更新</button>
</div>
<script >
new Vue({
el: '#app',
data() {
return {
list: [1, 2, 3]
}
},
mounted(){
Array.from(document.querySelectorAll('.ttt')).forEach(item=>{
item.dataset.mm = item.textContent;
})
},
methods:{
changeData(){
this.list = [4, 5, 6]
}
}
})
</script>
初始dom结构如下:
没有key情况下更新数组,然后DOM结构如下,可以看到dataset值没有变化,可以初步判断只是更新了div的内容
使用数组元素作为key情况下更新数组,然后DOM结构如下,可以看到dataset没有了,可以初步判断删除和创建了新的dom,很明显这样处理效率更低。
vue源码中的处理程序逻辑:
没有key的情况: updateChildren
-> 判定为同类型节点(div),执行patchVnode
方法 -> 再对div这个节点做updateChildren
处理 -> 继续patchVnode
,最后的结果就是更新文本节点内容;
if (oldVnode.text !== vnode.text) {
nodeOps.setTextContent(elm, vnode.text)
}
有key并且key不相等: updateChildren
-> key不同,判定不同类型节点(div),执行createElm
方法,进而创建新的dom节点;
为什么说使用index作为key容易出错
- 复现步骤:在第一行的Input里输入1,在第二行Input里输入2,然后点第一行的“ד删除第一行
- 期待结果:删除第一行后,应该变成“dog:2”
- 实际结果:删除第一行后,变成了“dog:1”
<script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
<div id="app">
<ul>
<li v-for="(item, index) in list" :key="index">
<test-row :name="item"></test-row>
<span style="color: red;cursor: pointer" @click="handleRemove(index)">X</span>
</li>
</ul>
</div>
<script>
Vue.component('test-row', {
template: `<span>
<span>{{ name }}:</span>
<input v-model="nums"/>
</span>`,
props: {
name: String
},
data() {
return {
nums: ''
}
}
})
new Vue({
el: '#app',
data() {
return {
list: ['cat', 'dog']
}
},
methods: {
handleRemove(i) {
this.list.splice(i, 1);
}
}
})
</script>
出现问题的原因:在patch阶段会认为这两个input子节点是sameVnode,进而复用原来的dom节点,因为组件只触发了更新,没有重新创建实例, 所以组件实例data数据没有变化,输入框内的内容就不会变化。
如果使用dog、cat作为key就可以避免这个bug;