谈谈vue中为什么不推荐用index作为key

180 阅读5分钟

相信大多数vue的初学者都有一个疑问,为什么不推荐用index作为key,要解答心中的疑问,就看看下面这篇文章吧

1.key的作用

首先我们要知道key是干什么的,我们先来看看官方的解释。

key 这个特殊的 attribute 主要作为 Vue 的虚拟 DOM 算法提示,在比较新旧节点列表时用于识别 vnode。

  • 预期number | string | symbol

  • 详细信息

    在没有 key 的情况下,Vue 将使用一种最小化元素移动的算法,并尽可能地就地更新/复用相同类型的元素。如果传了 key,则将根据 key 的变化顺序来重新排列元素,并且将始终移除/销毁 key 已经不存在的元素。

    同一个父元素下的子元素必须具有唯一的 key。重复的 key 将会导致渲染异常。

我的理解是,当数据源发生改变时,vue会重新生成一份虚拟DOM,通过diff算法来比较新旧虚拟DOM,,而key就是用来让diff算法更快速的知道哪两个节点进行比较。

2.什么是虚拟DOM

虚拟 DOM(Virtual DOM)是一种轻量级的 JavaScript 对象,它描述了真实 DOM 中的节点信息和属性。虚拟 DOM 可以在内存中进行操作,然后通过算法比较新旧虚拟 DOM 的差异,最终只对发生变化的部分进行 DOM 操作,从而提高了性能。

我们都知道传统的dom数据发生变化的时候,我们都需要不断的去操作dom,才能更新dom数据,虽然后面出现了模板引擎,可以让我们一次性更新多个dom。但模板引擎依旧没有一种可以追踪状态的机制,当引擎内某个数据发生变化时,它依然操作dom去重新渲染整个引擎。

而虚拟dom可以很好地跟踪当前dom状态,因为它会根据当前数据生成一个描述当前dom结构的虚拟dom,然后数据发生变化时,有生成一个新的虚拟dom,而两个虚拟dom恰好保存了变化前后的状态。然后通过diff算法,计算出当前两个虚拟dom之间的差异,得出一个更好的替换方案。

这整个过程就好像是代码生成了一份设计图,再根据这份设计图去浏览器绘制页面。当数据发生改变后,又会生成一份新的设计图,通过两张设计图的比较,就能知道更改的地方在哪里了。

我们来看一段代码

<div id="app">
  <p class="text">hello world!!!</p>
</div>

他会先生成下面的虚拟DOM

{
  tag: 'div',
  props: {
    id: 'app'
  },
  chidren: [
    {
      tag: 'p',
      props: {
        className: 'text'
      },
      chidren: [
        'hello world!!!'
      ]
    }
  ]
}

然后生成真实DOM。

3.什么是diff算法

diff算法就是进行虚拟节点对比,并返回一个patch对象,用来存储两个节点不同的地方,最后用patch记录的消息去局部更新Dom。

  特点:1.比较只会在同层级进行, 不会跨层级比较

        2.首先从上至下,从左往右遍历对象,也就是树的深度遍历,这一步中会给每个节点添加索引,便于最后渲染差异
        
        3.一旦节点有子元素,就去判断子元素是否有不同

image.png 在上图中,diff算法会从根节点开始比较,如果根节点变了,会删除整个DOM树,没变就复用,继续比较子结点,依次类推。

4.不推荐index作为key

我们来看下面的代码

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
   <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<style>
    li{
        list-style: none;
    }
</style>
<body>
    <div id="app" class="div">
        <p>用index作为key</p>
        <ul>
        <components v-for="(item,index) in list" :key="index">
        </components>
        </ul>
        <p>用唯一标识作为key</p>
        <ul>
            <components v-for="(item,index) in list" :key="item.id"> </components>
        </ul>
        
        <button @click="del">点击删除</button>
    </div>
</body>
<script>
    new Vue({ 

        el: '#app',
        data() {
            return {
                list: [
                    {
                        id: 1,
                        name: 'first',
                        num:1
                    },
                    {
                        id: 2,
                        name: 'second',
                        num:2
                    },
                    {
                        id: 3,
                        name: 'third',
                        num:3
                    },
                ]
            }
        },
        methods: {
            del(){
this.list.shift()
            }
        },
        components:{
            components:{
                template:`<div>{{Math.floor(Math.random()*10)}}</div>`
            }
        }
    })
    
</script>

</html>

image.png image.png

我们可以看到开始的时候没有任何区别,但当我点击删除按钮后

image.png image.png 仔细观察,会发现,我们原本是打算删除第一个5,但是点击删除后,删除了最后的7!这就是使用index作为key可能会产生的后果。我们可以结合上面的虚拟dom和diff算法来知道原因:

当点击删除按钮后,数据源发生改变,会生成一份新的虚拟DOM,通过diff算法进行前后比较,此时发现key为0的结点存在,复用;key为1的结点存在,复用,key为2的结点不存在,把原先的结点删除,于是就造成了上面的结果。

而当使用id(唯一标识)为key时,就可以正确的对应上了,我们也可以得到想要的结果。

5.总结

1.虚拟DOM中key的作用

key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据新数据生成新的虚拟DOM, 随后Vue进行新虚拟DOM与旧虚拟DOM的差异比较,比较规则如下:

2.对比规则

(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:

①.若虚拟DOM中内容没变,直接使用之前的真实DOM

②.若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM.

(2).旧虚拟DOM中未找到与新虚拟DOM相同的key

创建新的真实DOM,随后渲染到到页面。

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

1.若对数据进行:逆序添加、逆序删除等破坏顺序操作会产生没有必要的真实DOM更新==>界面效果没问题,但效率低。

2.如果结构中还包含输入类的DOM,会产生错误DOM更新,导致界面有问题。

** 新手小白,欢迎指正!**