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项目中使用百度地图api的详细步骤

    Vue项目中使用百度地图api的详细步骤

    在之前的一个小项目中,用到的显示当地的地图功能,下面这篇文章主要给大家介绍了关于Vue项目中使用百度地图api的详细步骤,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2022-10-10
  • vue.js系列中的vue-fontawesome使用

    vue.js系列中的vue-fontawesome使用

    这篇文章主要介绍了vue.js系列中的vue-fontawesome使用,需要的朋友可以参考下
    2018-02-02
  • vue3无法使用jsx的问题及解决

    vue3无法使用jsx的问题及解决

    这篇文章主要介绍了vue3无法使用jsx的问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-09-09
  • 详解mpvue开发微信小程序基础知识

    详解mpvue开发微信小程序基础知识

    这篇文章主要介绍了详解mpvue开发微信小程序基础知识,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09
  • 解决ant Design Search无法输入内容的问题

    解决ant Design Search无法输入内容的问题

    这篇文章主要介绍了解决ant Design Search无法输入内容的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • vue动态生成dom并且自动绑定事件

    vue动态生成dom并且自动绑定事件

    本篇文章主要介绍了vue动态生成dom并且自动绑定事件,具有一定的参考价值,有兴趣的可以了解一下。
    2017-04-04
  • vue添加自定义右键菜单的完整实例

    vue添加自定义右键菜单的完整实例

    这篇文章主要给大家介绍了关于vue添加自定义右键菜单的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • vue实现与安卓、IOS交互的方法

    vue实现与安卓、IOS交互的方法

    这篇文章主要介绍了vue实现与安卓、IOS交互的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-11-11
  • vue页面跳转后返回原页面初始位置方法

    vue页面跳转后返回原页面初始位置方法

    下面小编就为大家分享一篇vue页面跳转后返回原页面初始位置方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-02-02
  • 详解Vue中表单组件的双向数据绑定

    详解Vue中表单组件的双向数据绑定

    Vue 提供了双向数据绑定机制,使得开发者可以轻松地将表单组件的值与 Vue 实例中的数据进行关联,本文将详细介绍如何在 Vue 中使用这些表单组件,并实现双向数据绑定,需要的可以参考下
    2024-03-03

最新评论