带你了解vue源码与实用技巧(三)—— key

139 阅读3分钟

我们在使用v-for时,需要给单元上加上key

<ul>
    <li v-for="item in items" :key="item.id">...</li>
</ul>

key是每个vnode的唯一id,也是diff的一种优化策略,可以根据key,更快的找到对应的节点,更高效的更新vnode,避免频繁更新不同元素,减少dom操作,提高性能

如上图,插入E,通过对比分析使用key与不适用key的区别。

使用key

每次循环新老树对比,将一样的元素放入相应的位置

A B C D

A B E C D

  • 1次循环,比较A,相同节点,patchA,不发生dom操作

B C D

B E C D

  • 2次循环,比较B,相同节点,patchB,不发生dom操作

C D

E C D

  • 3次循环,C, E不同节点,比较D,相同节点,patchD,不发生dom操作

C

E C

  • 4次循环,比较C,相同节点,patchC,不发生dom操作

oldCh 全部处理结束,newCh 中只剩下 E,创建 E 并插入到 C 前面

结论:共进行了4次 patch,1次追加新dom的操作

不使用key

通过vue源码来看,未设置key,key=undefined,undefined 是恒等于 undefined,会人为这是相同节点,新旧vnode 进行diff,然后将对比出来的结果更新真是dom

A B C D

A B E C D

  • 1次循环,比较A,相同节点,patchA,不发生dom操作

B C D

B E C D

  • 2次循环,比较B,相同节点,patchB,不发生dom操作

C D

E C D

  • 3次循环,比较C、E,相同节点,进行 patch,数据不同,发生dom操作

D

C D

  • 4次循环,比较D、C,相同节点,进行 patch,数据不同,发生dom操作

oldCh 全部处理结束,newCh 中剩下 D,创建 D 并插入 dom 中

结论:共进行了4次 patch,2次更新,1次追加新dom的操作

设置key值一定能提高diff效率吗?

答案:不一定

这种模式默认高效,但是只适用于不依赖子组件状态或临时 DOM 状态

建议尽可能在使用 v-for 时提供 key,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升

不能用index做key

影响性能:当用index作为key的时候,删除节点后面的所有节点都会导致重新渲染,因为index变化了,可以也就变化了

结论

1、key的作用主要是为了高效更新vnode。其原理:vue在patch过程中通过key可以精准判断两个节点是不是同一个节点,从而避免频繁更新不同的元素,使得整个pathch 更加高效,减少dom操作,提高性能

2、另外,若不设置key还可能在列表更新时候引发一些隐藏的bug。
3、vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。

vue 中有关于 key

key

在vue中,列表渲染的时候需要我们定义key。那么key在vue中有什么作用呢。

key主要作用于Vue的virtual DOM算法,在diff new nodes list和old nodes list时,作为识别VNode的一个线索

那么知道key是用于 dom diff算法中使用的,也就是说当key发生改变的时候会触发组件重新渲染更新

使用key的改变,使animate动画重新触发

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <link
      rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.0/animate.min.css"
    />
    <style>
      #box {
        width: 100px;
        height: 100px;
        background-color: blanchedalmond;
        margin: 100px;
      }
    </style>
  </head>
  <body>
    <div id="app1"></div>
    <div id="app2"></div>
    <script>
      const app1 = new Vue({
        template: '<div id="box" :class="className" @click="reanimate"></div>',
        data() {
          return {
            className: "animate__animated animate__bounce",
          };
        },
        methods: {
          reanimate() {
            var _this = this;
            this.className = "";
            setTimeout(() => {
              _this.className = "animate__animated animate__bounce";
            });
          },
        },
      });
      const app2 = new Vue({
        template:
          '<div id="box" :class="className" :key="key" @click="reanimate"></div>',
        data() {
          return {
            className: "animate__animated animate__bounce",
            key: 1,
          };
        },
        methods: {
          reanimate() {
            ++this.key;
          },
        },
      });
      app1.$mount("#app1");
      app2.$mount("#app2");
    </script>
  </body>
</html>