16_Vue列表渲染中key的工作原理和虚拟DOM对比算法

key的作用

  • 粗略的讲,key的作用就是给 节点 设置一个 唯一的标识
  • 就像我们人类社会中,每个人的身份证号一样

image-20221028115921544

  • 在大部分对key要求不是很严格的场景下,使用index作为key是没问题的
  • 但是我们本章要探讨的是,其他情况,可能会出现问题的情况

来看个例子

案例

  • 这里呢,有个ul标签

  • 在内部,li标签通过v-for渲染 data当中的persons数组

    • image-20221028120516257

    • <script>
          var vm = new Vue({
            el: '.app',
            data: {
              name: 'wavesbright',
              persons:[
                  {id:1,name:'张三',age:18},
                  {id:2,name:"李四",age:19},
                  {id:3,name:"王五",age:20},
              ],
            },
          });
      </script>
      
  • 得到了三个节点(3个li)

image-20221028120404442

    <div class="app">
        <h1>遍历数组</h1>
        <ul>
            <li v-for="(item,index) in persons" :key="index">
                {{item.name}} - {{item.age}}
            </li>
        </ul>
    </div>

我现在提一个需求

需求

  • 我添加一个按钮,这个按钮会给我添加一个 老刘 这个对象(老六)
  • 这个老刘呢,不能添加在 persons数组的最后面,要在最前面
  • 不然看不出问题

click方法只调用一次哈,多了不好分析,这里使用的是事件修饰符

image-20221028151050675

    <div class="app">
        <h1>遍历数组</h1>
        <!-- 添加一个人,叫老刘 -->
        <button @click.once="addPerson">添加</button>
        <ul>
            <li v-for="(item,index) in persons" :key="index">
                {{item.name}} - {{item.age}}
            </li>
        </ul>
    </div>

image-20221028152046132

<script>
    var vm = new Vue({
      el: '.app',
      data: {
        name: 'wavesbright',
        persons:[
            {id:1,name:'张三',age:18},
            {id:2,name:"李四",age:19},
            {id:3,name:"王五",age:20},
        ],
      },
      methods: {
        // 添加成员 == 老刘
        addPerson(){
            // 这是老刘
            var laoliu = {id:4,name:'老刘',age:'xx'}
            // 在数组顶部添加成员
            this.persons.unshift(laoliu)
        }
      },
    });
</script>

测试

感觉没什么问题呀,为什么会讲这个呢

10-28-1

警告,错误没有,页面显示正常,也没有报错

有问题吗,有,这里面有个很严重的问题

增加需求

  • 我现在再来一个需求
  • 给每个 li标签当中,添加一个input框
    • image-20221028151659493

再来测试下

10-28-2

没问题啊,咋地?来,我给你演示下

问题出现

  • 我在添加老刘之前,我先给张三李四王五的input框框中,输入一些文字
  • 然后添加老刘,我们来看看这次是什么样子的

测试

10-28-3

  • 我们希望看到的是什么? == 老刘出现的时候,是一个空白的input框
  • 但现在问题是什么?下方的三个兄弟的信息 分别错位了 为什么会这样
  • 那看看 现在我将 :key换成 item.id会怎么样

image-20221028152134062

再测试

10-28-4

哦~那我们现在可以总结一个情况

  • 使用index会出现这个问题
  • 但是使用自带的id就没这个问题,这是我们现在所遇到的情况

key的工作原理和对比算法

这里使用流程图讲解

分析index作为key

初始化流程

1、一切的一切都是你写了这段代码

image-20221028152501541

2、vue是不是会拿着你的数据生成 虚拟DOM?(并不是一开始就把数据给你变为页面DOM的,是有流程的)

image-20221028152603638

3、真实DOM上是没有这个key的,虚拟DOM上必须要有(没有,vue不能高效工作)

image-20221028152702102

4、上图这个是已经生成真实DOM了,我们现在还处于虚拟DOM生成的过程中(假设现在页面还没有生成这三个li标签)。现在,在内存当中是不是有这三个虚拟DOM了(3个li标签)

5、将虚拟DOM转换为真实DOM

image-20221028152919220

6、请问,用户是在哪里操作的数据?虚拟DOM还是真实DOM,用户输入的数据,残留在谁身上了?虚拟还是真实DOM?(用户操作的全是真实DOM)

image-20221028153050726

这个时候初始化流程就结束了

数据更新,老刘出现

1、新的数据出现了,老刘出现了,老刘是排在所有人的前面

image-20221028153229232

2、随后,会根据新的数据,生成新虚拟DOM因为数据发生改变了

image-20221028153331033

这个时候,老刘的key就是0了,因为你使用index作为索引,老刘排在最前面(因为我们插在最前面)

重点来了:在目前的整个流程当中,生成了两份虚拟DOM,vue不会根据上面这个修改后的虚拟DOM进行真实DOM的创建,而是会将两个虚拟DOM进行一个对比算法,这就是该算法的由来

image-20221028153810783

虚拟DOM的对比算法

对比的时候,依赖着这个key,怎么对比的呢?请让我用文字来为你形容

  1. 首先,它来到这个新的虚拟DOM当中,按照顺序,先取出第一个(老刘)
    • image-20221028154032326
    • 然后它就问,你的key是多少,老刘回答 0
  2. 接下来,它来到旧的虚拟DOM中,寻找和 老刘拥有相同key值的人 谁呢? 张三吖
    • image-20221028154246110
    • 找打了,咋的呢?
    • 它会对比两个节点的内容
  3. 怎么对比的呢?
    1. image-20221028154635165
    2. 拿出 右侧的 老刘-30 与 左侧的 张三-18 ,一对比,诶,两边不相等
    3. image-20221028154731806
    4. 不一样了,怎么着?我们可以得出一点,key值为0的两个虚拟DOM内容不相同
    5. 接下来,因为二者内部都存在 input,开始对比input的内容
    6. image-20221028154850508
  4. 这里先暂停下,我来问个问题?请问,对比二者的input的时候,它们input的内容是否相等?
    • 答案是相等的,因为这俩个都是虚拟DOM
    • 用户输入数据的时候,数据是残留在真实DOM中的
      • image-20221028153050726
    • 人家是在内存当中对比的虚拟DOM,
    • 最终input这里对比的结果就是相等的
    • image-20221028155106931
  5. 刚刚对比不一样(老刘-30与张三-18)的怎么办,一样的(input)怎么办呢?
    • 一样的结果就是 复用
  6. 什么是复用呢?
    • image-20221028155435461
    • 你看,我老刘这里有个input,你张三这里也有个input
    • 我老刘的key是0,你张三的key也是0
    • 那么作为唯一标识,咱俩是来自同一个体系的
  7. 要不你看这样吧,你这个张三的虚拟DOM一定转换过真实DOM(都用对比算法了肯定转换过)
    • image-20221028155613207
  8. 你看昂,张三的input转换过真实DOM,那么我老刘的input与张三的input是一样的(虚拟DOM中)
    • 那我老刘就没必要把 li 当中 的 input转换为 真实DOM了
      • image-20221028155809293
    • 我直接拿 张三的 input(真实) 复用
  9. 也就出现了下面的情况
    • image-20221028155903526
    • 虚拟DOM进行了对比
    • 对比的结果 决定了 还是使用之前 张三的 真实input (不需要将老刘的虚拟input转换为真实input)
  10. 然鹅,张三的真是input当中,还残留着用户的输入
  11. 那么,搬用过来的时候,把残留输入一起带过来了
  12. 错在哪里
    • 使用index作为key,导致了 复用 的发生,顺序乱了
    • 由于这个细节错误的出现,导致了错位的生成
  13. 以此类推,到王五的时候,key = 3在旧的虚拟DOM当中不存在,那么只有自己生成了
    • image-20221028160351939

我们出现这个错误的原因是执行了这个奇葩的需求,但是通过这个需求,我们能对key进行更加深入的理解

为什么说效率低

image-20221028160837061

  • 我们来看图,图里解释的很清楚了
  • 二者的input是相等的,在虚拟DOM当中
  • 但是二者的 插值语法这个位置
    • image-20221028160913980
  • 是不相等的,不相等,那么就没有办法采用 复用行为
  • 没有办法采用复用行为,那么 插值语法这里的真实DOM就需要自己生成
  • 也就是这一块,是新的虚拟DOM转换为真实DOM自己生成的
    • image-20221028161023662

分析item.id作为key

image-20221028161150534

通过上面的分析,这里思路就很清晰了

  1. 还是一样的,因为这些初始化数据,我们在内存当中生成了虚拟DOM
    • image-20221028161431657
  2. 虚拟DOM转换为真实DOM
    • image-20221028161439648
  3. 用户在真实DOM进行操作,残留了输入
    • image-20221028161448483
  4. 接下来我们添加了一个成员,老刘
  5. 那么虚拟DOM被更新了
    • image-20221028161458620
  6. 更新了,那么就会和旧的虚拟DOM进行对比
    1. 老刘的key是004,旧的DOM当中是没有key为004的元素的,那么老刘就需要自己生成
      • image-20221028161517890
    2. 张三的key是001,旧的虚拟DOM当中有吗?有的,那么input一样吗,一样的,那就复用呗
    3. 同理李四和王五也是
    4. image-20221028161524804
  7. 所以使用id作为key是不会出现刚刚那个问题的
  8. 效率高吗,高,因为服用了,错位了吗,没有

不写key

  • 如果你不写key,那么vue在遍历的时候会默认将索引值index作为key
  • 既然index作为key了,这个DOM身上是有key的,那么自然不会报错
  • 既然index作为key了,遇见刚刚那个问题自然会出现问题

面试题

react、vue中的key有什么作用?

分为如下几步回答

虚拟DOM中key 的作用

image-20221028161945156

虚拟DOM对比算法(为了复用节点)

image-20221028161959662

用index作为key可能会引发的问题

image-20221028162027090

开发中如何选择key

image-20221028162044189