为什么v-for不推荐使用index做为key

本文续接上篇 我终于搞懂了vue的diff算法

为什么v-for不推荐使用index做为key

引用这篇掘金文章中所列出的两种情况,本文只对分析过程做一些图示补充

1、节点reverse即数组倒置时

为什么v-for不推荐使用index做为key_第1张图片
假设如下代码,值直接以文本格式绑定在dom节点内部

 <div>
     <span v-for="(item,index) in arr" :key="index" >{{item}}span>
 div>

当使用index作为key时,首先生成的旧虚拟dom节点层级为:

div
  span A  key = 0
  span B  key = 1
  span C  key = 2
  span D  key = 3
  span E  key = 4

生成的新虚拟dom节点层级为

div
  span E  key = 0
  span D  key = 1
  span C  key = 2
  span B  key = 3
  span A  key = 4

span A key = 0 代表 span 标签内的文本是A,span 标签的 key 是 0,以下同理

  1. 比较第一层,即从div开始比较,div光秃秃的啥都没有,所以肯定符合sameVnode,调用patchVnode
  2. 新节点中不存在文本,新旧节点都存在子节点(本例中为子节点为span标签),调用updateChildren,传入子节点 即 span标签数组
  3. 按图中的顺序进行比较,但由于key是index,他们的key相等,因此第一个比较分支就符合sameVnode,(到此对应图中第一条绿色线)。然后调用patchVnode(执行顺序为图中黄色线部分),此时传入的两个节点分别为
    旧节点 A 和新节点 E
  4. 节点内存在文本,且文本不相同,直接使用新节点内的文本覆盖旧的(注意这里仅覆盖文本),即使用E直接覆盖A,节点变为
    E
  5. 后面的比较同理,由于前后数组长度不变只是倒置了,不存在新旧节点循环结束还有剩余节点未比较的情况出现,所以更新到此结束
    为什么v-for不推荐使用index做为key_第2张图片

那么正常以唯一标识作为key的情况是怎样呢,假设我们以值作为key,代码如下

 <div>
     <span v-for="item in arr" :key="item" >{{item}}span>
 div>
  1. 这次的执行过程前面基本一致,下面列出不同的地方
  2. 到updateChildren内进行比较的时候,命中了第四个判断,即旧尾结点和新首节点符合sameVnode,然后调用patchVnode
  3. 此时patchVnode时发现节点内文本相同,于是不进行任何操作。
  4. 回忆下之前说的,当旧尾和新首符合sameVnode,在patchVnode之后,还会把旧尾移动到旧首节点前,对应到真实dom中, span E也就跑到了 span A之前。
    为什么v-for不推荐使用index做为key_第3张图片
  5. 后面的步骤同上依次比较,最终结果也是 E D C B A

为什么v-for不推荐使用index做为key_第4张图片

所以看起来key用index,并没什么区别啊,反正最终结果是对的。
但实际上用index作为key的时候,我们去更新了dom元素,更新了节点的文本,但用唯一标识作为key时,只是简单的复用了dom,移动了dom的位置而已

2、节点删除场景

假设有如下代码: arr = ['A', 'B', 'C', 'D', 'E']

 <div v-for="(item, index) in arr" :key="index">
      <demo></demo>
 </div>

demo组件

<template>
  <div>{{ Math.random() }}</div>
</template>

假设Math.random()生成的随机数依次为1,2,3,4,5。
现在我们执行 arr.splice(0, 1),删除数组的第一个元素 A 。理想情况下,页面展示的应该是 2,3,4,5,但实际上页面展示的却是 1,2,3,4
为什么v-for不推荐使用index做为key_第5张图片

当key使用 index 时

生成的旧虚拟dom节点层级为:

demo 1 key = 0 代表 demo 组件内展示的随机数是 1,demo 标签的 key 是 0,以下同理

div
  demo 1  key = 0
  demo 2  key = 1
  demo 3  key = 2
  demo 4  key = 3
  demo 5  key = 4

生成的新虚拟dom节点层级为

div
  demo 2  key = 0
  demo 3  key = 1
  demo 4  key = 2
  demo 5  key = 3
  1. 比较第一层,即从div开始比较,div光秃秃的啥都没有,所以符合sameVnode,调用patchVnode

  2. 新节点中不存在文本,新旧节点都存在子节点(本例中为子节点为 demo 标签),调用updateChildren,传入子节点 即 demo 标签构成的子节点数组

  3. 按图中的五种逻辑分支比较依次进行比较,由于key是index,他们的key相等,因此第一个比较分支就符合sameVnode(key相同都是0,tag相同都是demo),(到此对应图中第一条绿色线)。

  4. 然后调用patchVnode,此时传入的两个节点分别为
    旧节点demo 1 key = 0和新节点 demo 2 key = 0

  5. 此时走 patchVnode 逻辑,新旧节点内都不存在文本,紧接着走后面的四步逻辑判断,发现四步都不命中,于是本次更新什么都不做。

  6. 那么本次更新后相当于直接复用了旧节点demo 1 key = 0 作为页面展示的第一个元素。

  7. 后面依次调用,由于新的虚拟dom节点中demo节点只有4个,所以本次循环以新节点率先循环完毕而结束,循环结束时旧节点还剩一个demo 5 key = 4 没有比对,直接删除多余的节点。
    在这里插入图片描述

  8. 最终更新后的节点就变成了

div
  demo 1  key = 0
  demo 2  key = 1
  demo 3  key = 2
  demo 4  key = 3

页面展示 1,2,3,4,本来要删除1的,结果错误的把5删掉了

本次更新过程如下图:
为什么v-for不推荐使用index做为key_第6张图片

使用唯一标识作为key的更新过程

 <div v-for="(item, index) in arr" :key="item">
      <demo></demo>
 </div>
div
  demo 1  key = A
  demo 2  key = B
  demo 3  key = C
  demo 4  key = D
  demo 5  key = E

生成的新虚拟dom节点层级为

div
  demo 2  key = B
  demo 3  key = C
  demo 4  key = D
  demo 5  key = E

为什么v-for不推荐使用index做为key_第7张图片

  1. 前两步都跟上面使用index作为key的步骤一致,到updateChildren逻辑,开始不同了,由于key不同,所以前四步比较都未命中。
  2. 接着走了图中绿色线标注的逻辑
    为什么v-for不推荐使用index做为key_第8张图片
    执行上述步骤后,demo 2 key = B 移动到了首位。
  3. 后面的节点 依上述逻辑依次更新,更新的结果为 2,3,4,5 符合预期

导致删除错误的原因,在于使用了index作为key,在updateChildren逻辑中,比较新旧节点的首节点,由于key是index,导致sameVnode在判断时认为两个节点是相似的,然后错误复用了节点。

你可能感兴趣的:(前端,javascript,vue.js,diff)