从源码看vue的key和状态错乱的patch

状态错乱

喜闻乐见的不提供key更新v-for的dom会导致状态错乱问题:

html 复制代码
<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接管到

如下代码:

html 复制代码
<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接管的属性不会发生错误

就类似这种情况:

html 复制代码
<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,原因如下

html 复制代码
<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的实现:

html 复制代码
<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说是业务驱动,但是我没有搞的太明白,等我搞明白了在更新上!

相关推荐
PedroQue999 小时前
uni-app x工程化实战:从页面堆到可维护架构
前端·uni-app
开开心心_Every10 小时前
免费压缩软件WinRAR无弹窗、广告干扰
java·开发语言·前端·spring·智能手机·word·winrar
特立独行的猫A10 小时前
用仓颉语言写桌面应用:cj-tauri 框架与 AI 笔记软件 cjnote 实战
前端
特立独行的猫A10 小时前
我为什么推荐用仓颉语言的cj-tauri 写桌面应用(附 cjnote 最小的markdown笔记AI软件)
前端
纸片人10 小时前
牛一越界,大屏 3 秒飞到它身边:我用真实卫星数据 + Three.js 做了个 3D 智慧牧场
前端
cc_瀚海知行10 小时前
工具调用 6|助手什么时候该停下来?
java·服务器·前端·人工智能·python·ai
溪语流沙10 小时前
【Web全栈进阶】Nginx反向代理 + HTTPS:正式上线
前端·vue.js·python·nginx·https·fastapi
vivo互联网技术10 小时前
域名埋点零侵入实时校验实践:基于代理转发与规则驱动的工具设计
前端·测试·监控·代理·埋点
喵个咪10 小时前
GoWind ShortPlay 拆解:Flutter 沉浸式 Feed 的播放器工程——生命周期、手势状态机与游客降级
前端·flutter·架构