状态错乱
喜闻乐见的不提供key更新v-for的dom会导致状态错乱问题:
<script setup lang="ts">
import {ref} from "vue";
let arr = ref([0,1,2,3,4,5])
function deleteArrItem() {
arr.value.splice(3,1)
}
</script>
<template>
<input type="text" v-for="i in arr">
<button @click="deleteArrItem">change</button>
</template>
<style scoped>
</style>
这个比较常见了,原因就是Patch的时候,由于没有key,不好判断新旧vnode中的两个元素是不是同一个的元素,对于相同类型的vnode,会直接认为可复用然后patch,导致删除的“3”的input没有被移除,而是最后一个input因为前四个都被认为可复用而认为多余给删除了!
但是出现这个错误也有个前提,那就是相关的数据没有被vue接管到
如下代码:
<script setup lang="ts">
import {ref} from "vue";
let arr = ref([0,1,2,3,4,5])
function deleteArrItem() {
arr.value.splice(3,1)
}
let arr2 = ref(Array.from({length: 6}))
</script>
<template>
<input type="text" v-for="i in arr" :value="arr2[i]">
<br>
<button @click="deleteArrItem">change</button>
</template>
<style scoped>
</style>
再次执行发现并不会存在相同的问题,这是因为value被vue接管到了,导致vue能够正常处理属性
从源码来看(这里就不贴源码了,大家可以自己git下来读一下):在renderer.ts的patchElement中,在PatchChildren完成后会根据patchFlag来patch props,由于vue已经接管了value的状态,所以实际上vnode的信息是正确的,只是patch的时候patch错了而已,但是即使此时删除的是最后一个input,但是经过错位,但是正确vnode信息的patch,最后表现还是正常的(这里有点绕,可以借助compiler模块的编译逻辑理解)
这个问题根源在于:patch出错但是虚拟dom的描述是没有问题的,因此vue接管的属性不会发生错误
就类似这种情况:
<script setup lang="ts">
import {ref} from "vue";
let arr = ref([0,1,2,3,4,5])
function deleteArrItem() {
arr.value.splice(3,1)
}
</script>
<template>
<div v-for="i in arr">{{i}}</div>
<button @click="deleteArrItem" >change</button>
</template>
<style scoped>
</style>
但是错位patch性能不如提供key的正确的patch,因为后几个input的状态没有变化嘛,错位patch会让后几个也会被patch
这也是正常工作中很少出现这个问题的原因,因为毕竟要接管元素想要的状态嘛,不然放他在这里干嘛?
如果对compile模块和runtime模块的工作如何配合不清晰的可以看我的这篇文章:todo
组件的v-for
但是组件的v-for一定要提供key,原因如下
<script setup lang="ts">
import {ref} from "vue";
import InputCom from "@/component/InputCom.vue";
let arr = ref([0,1,2,3,4,5])
function deleteArrItem() {
arr.value.splice(3,1)
}
let arr2 = ref(Array.from({length:6}))
</script>
<template>
<input-com v-for="i in arr" :msg="arr2[i]"></input-com>
<button @click="deleteArrItem">change</button>
</template>
<style scoped>
</style>
InputCom的实现:
<script setup lang="ts">
import {ref} from "vue";
let value1 = ref(0)
let props = defineProps(['msg'])
</script>
<template>
{{props.msg}}
<input type="text" v-model="value1">
</template>
<style scoped>
</style>
此时还会出现问题,即使vue已经接管了所有的状态
这是由于patch component children的时候,并不管组件内部的实现,是相同的vnode就会按照刚才说的舍弃最后一个的方式进行patch,那这样在component上就会有大问题了,component内部的状态也是相同的:最后一个被删除了
和上一个例子不同的地方在于:一个是component的内部状态,一个是props状态,内部状态会随着patch的出错而错误移除最后一个component!
就像是上面的例子,组件内部的msg会保持正确!
key的作用?我要是不加key呢?
源码中看key用来判断两个vnode是不是同一个vnode
vue2不允许列表渲染没有key,但是vue3允许,这个改变在于,对于vue3的改进的pactch算法来说也允许不存在key,对应patchUnkeyedChildren,不加key对于没有状态的元素和组件更新更加高效,因为不需要繁琐的pacth,只需要就地更新就好!
只有v-for的patch需要key?其他的呢?
一般来说v-for, v-if会需要key,因为dom不存在稳定性,会发生dom移除增加、顺序错乱,此时key就很重要
但是对于稳定的dom,也就是不会出现增加新dom、删除dom、顺序错乱,所以此时key就不关键了
为什么vue自己不把key加上?
加了,v-if就加了,但是v-for说是业务驱动,但是我没有搞的太明白,等我搞明白了在更新上!