前言
记一下最近面试中被问到关于如果vue中循环不加key有什么问题
问题
首先我们有一点肯定知道,就是设置key可以优化diff算法,(这里看vue3代码),vue3对没有key的更新是执行patchUnkeyedChildren函数
代码节选
// 旧子节点
c1 = c1 || EMPTY_ARR
// 新子节点
c2 = c2 || EMPTY_ARR
const oldLength = c1.length
const newLength = c2.length
const commonLength = Math.min(oldLength, newLength)
let i
for (i = 0; i < commonLength; i++) {
const nextChild = (c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i]))
patch(
c1[i],
nextChild,
container,
null,
parentComponent,
parentSuspense,
namespace,
slotScopeIds,
optimized,
)
}
上面代码是vue3的源码,会发现如果没有key的话,就是全量替换,但真实情况可能不需要全量替换,这是加key一种优化
还有一个问题就是不加key的问题,看下面代码
<template>
<div>
<ul>
<button @click="add">add</button>
<li v-for="(item,index) in list" >
<input type="checkbox" >
{{ item.name }}
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const list = ref([
{ name: '小张',id:1},
{ name: '小七',id:2},
{ name: '小三',id:3},
])
const add = () => {
list.value.unshift({name:'小五',id:4})
}
</script>
<style lang='scss' scoped></style>
页面非常简单
当我们选择第一项的时候,如果再点击添加的时候会发现下面这种情况
会发现选中的是第一个,而不是小张(用索引当key也是一样),是因为底层此时是用循环索引判断,对于一开始的来说选中的是索引为0,添加之后小五是索引为0的,所以选中小五(key用索引同理),但是当key使用id的话,再看结果
就发现结果是我们想要的,这是因为此时id唯一值,而我们发现源码中计算有key的时候执行
while (i <= e1 && i <= e2) {
const n1 = c1[i]
const n2 = (c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i]))
# 判断两个节点是否一样,一个是type(标签类型)一个是key
if (isSameVNodeType(n1, n2)) {
# // 节点相同
patch(
n1,
n2,
container,
null,
parentComponent,
parentSuspense,
namespace,
slotScopeIds,
optimized,
)
} else {
# // 相同前置节点(a b)的更新处理完成
break
}
i++
}
isSameVNodeType函数是判断key值和标签类型,如果一样才会替换,否则不进行替换,所以上面选择是没有问题的