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

相关推荐
里欧跑得慢38 分钟前
CSS 模块化架构的演进:BEM、CSS Modules 到 CSS-in-JS 的反思
前端·css·flutter·web·css-in-js
IT_陈寒1 小时前
Vue的computed属性把我坑惨了,原来我一直用错姿势
前端·人工智能·后端
小灰灰搞电子1 小时前
Rust+Slint 实现温度计源码分享
前端·rust·slint
计算机魔术师2 小时前
面壁智能 OpenBMB 推出 MathForm,面向 Lean 4 数学自动形式化的开源框架、数据集与模型
前端
NeilCarmack2 小时前
Deepseek-harness增加桌面版端序列:第 2 讲 · spawn Electron:当前进程如何“交棒“
前端·javascript·electron
上海魁鲸科技有限公司3 小时前
APS高级排产系统到底有什么用?一文讲清功能、选型与落地建议
前端·microsoft·excel
陈随易3 小时前
Bun v1.4 更新总结:把浏览器、图片、定时任务和工程工具都装进一个运行时
前端·后端·程序员
东风破_4 小时前
TypeScript 高级类型进阶:keyof、Exclude、Record 与类型组合思想
前端·后端·typescript
DS随心转插件5 小时前
Grok生成的html怎么导出——AI导出鸭:大模型结构化输出的“最后一公里”工程化解构
前端·人工智能·ai·html·豆包·deepseek·ai导出鸭
এ慕ོ冬℘゜5 小时前
使用 jQuery 动态渲染表格与状态切换
前端·javascript·jquery