Vue中key为index和id的区别示例详解

 更新时间:2023年06月13日 10:01:48   作者:Lakeiedward  
这篇文章主要介绍了Vue中key为index和id的区别详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

一、Diff算法

在了解key的作用之前,先简单认识一下diff算法👇

diff算法的特点是平级比较,采用双指针和递归的方式进行逐级比较。

Vue会有一个根节点,先判断根节点是否是文本节点,如果不是文本节点,则会判断是否都有儿子节点,如果都有并且新旧儿子节点不相等,此时就会比较这两个新旧儿子节点(updateChildren),在做比较的时候会有以下几种情况:

  • 头头对比
  • 尾尾对比
  • 头尾对比
  • 尾头对比
  • 乱序对比,根据旧节点会生成一个映射表(也就是map对象),用新的节点一个个在映射表里找,没有的话插入,有的话移动复用,多余的删掉。

二、Key的作用

在比较两个节点的时候sameVnode(oldStartVnode, newStartVnode),主要根据key进行判断两个元素是否是一个元素,key不相同的话则说明不是同一个元素。使用key的时候尽量保持key的唯一性(这样可以优化diff算法)

动态列表添加的key的时候,要避免使用索引(index)!

接下来,我们使用数组渲染一组儿子节点小li,并且通过事件在数组的头部增加(unshift)一个数据;当key为index的时候,我们查看下图图片渲染的情况发现所有的小li都变化了,而key为id的时候,则只在li的最前面新加了一个小li,这就是diff算法根据key判断产生的差异性,具体在下面来看一看。

三、Key为Index

1) 图解

如下图,首先上面是旧节点,下面是新节点,新节点上在数组最前面新加了一个C节点,因为key是index,所以此时C的key还是0,但是文本是C,并不是A。

因为第一个新旧节点的key相同,所以此时会先进入到头头对比中,而不会进入到尾尾对比,在对比的过程中,会再次进入到patchVnode方法中判断新旧节点的文本是否一致,如果一致则直接复用,不一致则会对dom进行操作,将旧节点文本替换成新节点文本node.textContent = text

第一组对比完成之后,新旧节点的索引会依次增加,对比第二组,第二组的key也是一样的,会重复第一组的对比方式,最后将旧节点文本替换成新节点文本node.textContent = text

此时因为旧节点的开始索引和结束索引相等,则会退出while循环,根据判断新旧节点的开始和结束索引得出,最后一个剩余的新节点会插入(addVnodes)到A元素后面去。

此时更新就结束了,会发现进行了三次dom操作,虽然新旧节点除了新增的C节点,其他都是相同的,但是都没有复用原来的节点,而是直接使用textContent改变文本,所以index作为key不中!

2) 完整的步骤

看下一个完整的步骤:

  • 如果key是index,在头部添加一个节点,新加的节点key还是0,和第一个旧节点是一样的key(但是文本不一样),sameVnode就会判断他们俩是一样的节点,就会头头对比(而不是尾尾对比),此时虽然key相同的了,但是会递归进入到patchNode中时,会判断文本是否相同(key为index时,文本不相同),如果不相同,则会进行dom文本替换,把旧的文本替换成新的文本,就会出现上图所有的小li进行更新。
  • 以上步骤会一直重复头头对比,虽然每次对比时,key都是一样的,但是文本内容不一样,则会一直触发dom更新操作,也就是类似lis[0].textContent = 'C',一直到循环结束oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx,此时把多的新节点添加(addVnodes)进去,或者多的老节点删除(removeVnodes)掉。

四、Key为Id

1) 图解

如下图,新加的节点key为c,当进行sameVnode(oldStartVnode, newStartVnode)对比的时候,发现key不一样

则开始尾尾对比sameVnode(oldEndVnode, newEndVnode),此时key是一样的,则进入到patchVnode方法,判断新旧节点的文本是否一致,一致的话,就复用原来的节点了

对比完第一组,此时新旧节点的尾索引减1,还是尾尾相等,开始尾对比,重复上述的步骤,复用原来的旧节点,没有dom操作。

>

对比完第二组,旧节点的头索引和尾索引相等,则结束while循环,最后一个剩余的新节点会插入(addVnodes)到A元素前面去。

以上的步骤完成之后,只有最后一次执行了插入dom操作,优化了diff算法和减少了dom操作

2) 完整的步骤

完整的步骤:

  • 如果key是唯一的id,向前追加一个,sameVnode判断新旧节点时发现新旧节点的key不相同,开始尾对比,尾对比会进入到patchVnode方法,当为文本节点时,判断新旧节点的文本是否相同,结果发现相同,则不做更新dom操作,直接复用原来的,一直到循环结束oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx,此时只需要把多的新节点添加(addVnodes)进去,或者多的老节点删除(removeVnodes)掉即可,没有多余的dom操作。

五、源码

粘贴一下部分的Vue源码

1)sameVnode

只会判断key、 tag、是否有data的存在、是否是注释节点、是否是相同的input type,来判断是否可以复用这个节点。

function sameVnode(a, b) {
  return (
    a.key === b.key &&
    a.asyncFactory === b.asyncFactory &&
    ((a.tag === b.tag &&
      a.isComment === b.isComment &&
      isDef(a.data) === isDef(b.data) &&
      sameInputType(a, b)) ||
      (isTrue(a.isAsyncPlaceholder) && isUndef(b.asyncFactory.error)))
  )
}
function sameInputType(a, b) {
  if (a.tag !== 'input') return true
  let i
  const typeA = isDef((i = a.data)) && isDef((i = i.attrs)) && i.type
  const typeB = isDef((i = b.data)) && isDef((i = i.attrs)) && i.type
  return typeA === typeB || (isTextInputType(typeA) && isTextInputType(typeB))
}

2)patchVnode

如果新 vnode 不是文字 vnode

  • 那么就要开始对子节点 child 进行对比了。

如果新旧 children 都存在(都存在 li 子节点列表,进入 )

  • 那么就是 diff算法 想要考察的最核心的点了,也就是新旧节点的 diff 过程。

如果有新 children 而没有旧 children

  • 说明是新增 child,直接 addVnodes 添加新子节点。

如果有旧 children 而没有新 children

  • 说明是删除 child,直接 removeVnodes 删除旧子节点

如果新 vnode 是文字 vnode

  • 就直接调用浏览器的 dom api 把节点的直接替换掉文字内容就好。
function patchVnode(
  oldVnode,
  vnode,
  insertedVnodeQueue,
  ownerArray,
  index,
  removeOnly?: any
){
  ...
  // 判断新节点是不是text节点
  if (isUndef(vnode.text)) {
  // 不是text节点
    if (isDef(oldCh) && isDef(ch)) {
      // 老节点和新节点都有child,并且child不相等
      if (oldCh !== ch)
        updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
    } else if (isDef(ch)) {
      // 新节点有child,老节点没有,则新增
      if (__DEV__) {
        checkDuplicateKeys(ch)
      }
      if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
      addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
    } else if (isDef(oldCh)) {
      // 老节点有child,新节点没有,则删除
      removeVnodes(oldCh, 0, oldCh.length - 1)
    } else if (isDef(oldVnode.text)) {
      nodeOps.setTextContent(elm, '')
    }
  } else if (oldVnode.text !== vnode.text) {
    // 是text节点并且文本不一样,就把旧的文本替换成新的文本
    nodeOps.setTextContent(elm, vnode.text)
  }
  ...  
}

Tips: 儿子节点不是文本时,一方有儿子,一方没有儿子(删除、添加),两方都有儿子,则进入diff算法对比

六、总结

  • 动态列表添加的key的时候,要避免使用索引(index)
  • 使用唯一的key可以优化diff算法,减少更新dom的操作

相关文章

  • vue实现动态控制表格列的显示隐藏

    vue实现动态控制表格列的显示隐藏

    这篇文章主要为大家详细介绍了vue实现动态控制表格列的显示隐藏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04
  • Vue中自定义标签及其使用方式

    Vue中自定义标签及其使用方式

    这篇文章主要介绍了Vue中自定义标签及其使用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • vue 如何引入本地某个文件 require

    vue 如何引入本地某个文件 require

    这篇文章主要介绍了vue 如何引入本地某个文件 require,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • vue实现视频全屏切换功能

    vue实现视频全屏切换功能

    这篇文章主要为大家详细介绍了如何使用vue实现视频全屏切换的功能,文中的示例代码讲解详细, 具有一定的借鉴价值,有需要的小伙伴可以参考一下
    2023-11-11
  • 关于json-bigint处理大数字问题

    关于json-bigint处理大数字问题

    这篇文章主要介绍了关于json-bigint处理大数字问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • Vue-scoped(局部)样式使用方法及实例代码

    Vue-scoped(局部)样式使用方法及实例代码

    这篇文章主要介绍了Vue-scoped(局部)样式使用方法及实例代码,文中示例代码介绍了的非常详细感兴趣的同学可以参考阅读一下
    2023-05-05
  • 如何使用sm4js进行加密和国密sm4总结

    如何使用sm4js进行加密和国密sm4总结

    近期由于公司项目的需要开始研究国密SM4加密,下面这篇文章主要给大家介绍了关于如何使用sm4js进行加密和国密sm4的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-04-04
  • vue中 this.$set的使用详解

    vue中 this.$set的使用详解

    这篇文章主要为大家介绍了vue中 this.$set的使用,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2021-11-11
  • vue项目初始化过程中错误总结

    vue项目初始化过程中错误总结

    在Vue.js项目初始化和构建过程中,可能会遇到多种问题,首先,npm install过程中报错,如提示“No such file or directory”,建议删除package-lock.json文件后重新安装,在build或run时,若出现core-js相关错误
    2024-09-09
  • 前端vue框架select下拉数据量过大造成卡顿问题解决办法

    前端vue框架select下拉数据量过大造成卡顿问题解决办法

    这篇文章主要给大家介绍了关于前端vue框架select下拉数据量过大造成卡顿问题解决办法,文中通过示例代码介绍的非常详细,对大家学习或者使用select具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-07-07

最新评论