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

相关推荐
牧艺37 分钟前
cos-design v3.0:从 15 个 Demo 到 49 个组件的视觉特效库
前端·视觉设计
lichenyang45339 分钟前
ASCF 架构升级总览:WebRuntimePage 为什么要变薄
前端
道友可好39 分钟前
从今天开始:你的第一个 Harness Engineering 实践
前端·人工智能·后端
Linsk41 分钟前
组件 = 模板 + 业务逻辑
java·前端·vue.js
二月龙1 小时前
移动端 H5 页面开发:响应式适配 + 低版本兼容实战指南
前端
小强19881 小时前
HTML5 新表单全解:日期、手机号、颜色选择器
前端
妙码生花1 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(二):目录结构、初始化 GIT、设计并开发配置系统
前端·后端·go
鱼人1 小时前
HTML5 本地存储终极指南
前端
超绝大帅哥2 小时前
React的Fiber是什么? Vue为什么不需要Fiber ?
前端
yingyima2 小时前
正则表达式分组与捕获:凌晨3点服务器报警的解决方案
前端