Vue中key是干嘛的?和diff有什么关系?

406 阅读5分钟

前言

在我们使用vue的v-for去循环一个列表的时候,要求去填写一个key,都说写了key性能会更好,那这究竟是为啥呢,本文就带大家去了解这里面的真相

分析

我们先看第一段代码

<script setup>
import { ref } from "vue";

const list = ref([1, 2, 3]);

const change = () => {
  list.value.reverse();
};
</script>

<template>
  <ul>
    <li v-for="(item, index) in list">{{ item }}</li>
  </ul>
  <button @click="change">修改</button>
</template>

这段代码很简单就是一个列表加一个按钮,按钮的效果就是反转列表

动画.gif

接下来我们去观察dom的渲染

动画.gif

可以看到,第一个和第三个重新的渲染了,但是第二个没有重新渲染

这是由于我们没有给li设置key,那么默认vue3会给他去使用index做key,也就是说,现在三个li的key分别是0、1、2,我们将数组的反转以后,那么原来key是0的li现在内容就换成2的,原来key2的li的内容就换为了0,其中2的key不变,也就可以复用了

接下来我们修改数据,将他们都设置唯一的id,并将id作为key

<script setup>
import { ref } from "vue";

const list = ref([
  {
    id: 1,
    data: 1,
  },
  {
    id: 2,
    data: 2,
  },
  {
    id: 3,
    data: 3,
  },
]);

const change = () => {
  // list根据data排序
  list.value.sort((a, b) => a.data - b.data);
};
</script>

<template>
  <ul>
    <li v-for="(item, index) in list" :key="item.id">{{ item.data }}</li>
  </ul>
  <button @click="change">修改</button>
</template>

这样就可以得到复用,因为每个数据的key没有变,所以vue会将他能复用

虚拟dom

虚拟DOM(Virtual DOM)是一种用于提高Web应用性能的技术。它的核心思想是:

  1. 在内存中保持一个与真实DOM结构完全一致的虚拟DOM树。

  2. 当数据发生变化时,根据新的数据重新构建一棵新的虚拟DOM树。

  3. 然后将新旧两棵虚拟DOM树进行对比,找出差异。

  4. 最后只更新实际发生变化的DOM节点,而不是重新渲染整个页面。虚拟DOM(Virtual DOM)是一种用于提高Web应用性能的技术。它的核心思想是:

  5. 在内存中保持一个与真实DOM结构完全一致的虚拟DOM树。

  6. 当数据发生变化时,根据新的数据重新构建一棵新的虚拟DOM树。

  7. 然后将新旧两棵虚拟DOM树进行对比,找出差异。

  8. 最后只更新实际发生变化的DOM节点,而不是重新渲染整个页面。

Diff算法

Diff (Difference)是虚拟DOM中一项非常关键的技术,它用于比较新旧两棵虚拟DOM树之间的差异。

流程是

  1. 同层比较

    • Diff 算法会首先比较新旧两棵虚拟 DOM 树的同一层级节点。
    • 比较的原则是:tag 类型相同且 key 值相同的节点被认为是相同节点。
  2. 分类处理

    • 基于上一步的比较结果, Diff 算法会将节点分为以下 3 类:

      • 新增节点
      • 删除节点
      • 移动/更新节点
  3. 新增节点

    • 如果在新的虚拟 DOM 树中发现了在旧的虚拟 DOM 树中不存在的节点,则将其标记为新增节点。
    • 对于新增节点,Diff 算法会在真实 DOM 中创建并插入这个新节点。
  4. 删除节点

    • 如果在旧的虚拟 DOM 树中发现了在新的虚拟 DOM 树中不存在的节点,则将其标记为删除节点。
    • 对于删除节点,Diff 算法会在真实 DOM 中移除这个节点。
  5. 移动/更新节点

    • 如果节点的 tag 类型相同且 key 值相同,则认为是同一个节点,需要进行移动或更新操作。
    • Diff 算法会比较该节点的属性,如果有变化则更新真实 DOM 上对应的属性。
    • 同时 Diff 算法还会识别出节点顺序的变化,对这些节点进行移动操作,减少不必要的 DOM 操作。
  6. 递归比较

    • Diff 算法会递归地比较新旧虚拟 DOM 树的子节点,重复上述步骤,直到所有节点都比较完毕。

并且阅读源码我们可以看到

function sameVnode (a, b) {
  return (
    a.key === b.key && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}

在对比两个节点是否相同首先对比的就key,。也就是说key不同的dom就直接认为是不同的dom,只有key相同了才有下一步比较的必要,这也就是为什么key如此的重要了

提问

  • 可以使用index作为key吗?

不建议,在diff算法中判断两个节点是否相同,首先判断的就是两个节点的key是否相同,如果用index作为key,index不会随着元素位置的变更而移动,从而导致相同可复用的节点被认为不相同,降低了dom的复用性

  • 可以使用随机数作为key吗?

不可以,使用了随机数,dom将毫无复用可言

  • 虚拟dom优点
    • 跨平台
      • 跨平台是其最大的优点,将dom虚拟成对象,这样具有通用性,这样就可以根据虚拟dom去生成其他平台的代码
    • 分摊了浏览器渲染线程的性能开销,减少了回流重绘
      • vue不会动一次虚拟dom就渲染页面一次,而是在一段时间的操作之后,再去生成对应的真实dom
    • 开销很多的v8算力
      • 虚拟dom虽然让浏览器的渲染减少了回流和重绘的次数,但是这里的页面的变化都是通过虚拟dom完成的,需要js引擎去做计算