Vue前端框架——列表渲染时key的重要性

说到列表大家肯定都不陌生,在 Vue 中如果想将一组数据展示在页面中那就要用到列表渲染,但是大家经常忽略或者不重视列表渲染中的一项配置,那就是 key

在 Vue 中如果用到了列表渲染,但忘了或者没有为所渲染的 DOM 元素指定 key 的话, Vue 会默认将数组中每个数据的索引(index)作为key。如果只用索引为 key 绑定值,那么在某些情况下很可能会为整个程序造成不必要的麻烦。

除了使用索引作为 key还有一种方式就是使用每条数据的唯一标识(id)作为 key

下面的代码是基础代码,后续在讲解具体内容时会进行相应的修改。同时为了更好的演示这两种 key 的区别,在 <li> 标签内每个数据的后边都填加了输入框。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <script src="./vue.js"></script>
    <title>列表渲染中key的原理</title>
</head>
<body>
    <div id="root">
        <h2>汽车列表</h2>
        <button @click.once="addCar">添加一个品牌</button>
        <ul>
            <li v-for="car in cars">
                {{ car.brand }}--{{car.price}}
                <input type="text">
            </li>
        </ul>
    </div>

    <script>
        Vue.config.productionTip = false

        new Vue({
            el: "#root",
            data: {
                cars: [
                    {id: "01", brand: "奔驰", price: "45W"},
                    {id: "02", brand: "奥迪", price: "50W"},
                    {id: "03", brand: "宝马", price: "55W"},
                ]
            },
            methods: {
                addCar() {
                    this.cars.unshift({
                        id: "04", 
                        brand: "大众", 
                        price: "15W"
                    })
                }
            },
        })
</script>
</body>
</html>

将索引作为key

修改 <li> 部分的代码,将索引作为 key

<li v-for="(car, index) in cars" :key="index">
    {{ car.brand }}--{{car.price}}
    <input type="text">
</li>

程序运行后浏览器页面显示结果正常,控制台也没有任何报错和警告。

接下来在输入框中手动输入一些内容来作为每条数据的唯一标识,目前还没点击按钮,所以页面中显示的数据是已经在 data 中定义好的了。

 

 

然后点击按钮触发 addCar 方法,随后将一条数据添加到 cars 数组中,同时页面重新渲染后显示新的内容。页面列表中左半部分看起来一切正常,但右半部分看起来已经乱套了。按逻辑最后一个空的输入框应该在列表的最上边,其他三个依次向下错一位。

这就是使用索引作为 key 的一个坑,如果没有输入框光看列表左半部分的话一切都很正常。

下图展示了程序在第一次运行时所经历的过程,可以看到从虚拟 DOM 到真实 DOM 的转换后,输入框中是没有数据的,数据确实是后来手动输入的。

 

观察上图右半部分,在点击按钮添加一个新数据后,新数据被添加到了原数组中。Vue检测到原数据发生变化后根据新的数据生成新的虚拟 DOM,注意,此时我并没有画将新的虚拟 DOM 转换成真实 DOM 这一个步骤,而是在初始虚拟 DOM 与新虚拟 DOM 之间添加了一个双向箭头并配了描述文字虚拟 DOM 对比算法,问题就出现在这,接下来就主要说说新旧虚拟 DOM 之间究竟是如何进行对比的以及新虚拟 DOM 最后如何转换成的真实 DOM 的。

 

 

在新虚拟 DOM 转换为真实 DOM 之前要和旧虚拟 DOM 进行对比,主要是对比一下这两个虚拟 DOM 之间是否有相同的 DOM 元素,如果有那就将旧虚拟 DOM 中已经转换为真实 DOM 的元素直接放到页面,这样新虚拟 DOM 就不用再进行转换了,达到了一个复用的效果,更有助于提升渲染效率。

在列表渲染中新旧虚拟 DOM 是如何进行对比的呢?由于在列表渲染中每个被渲染出来的 DOM 元素都有自己的 key,那么在进行对比的时候新虚拟 DOM 会依次拿着元素的 key 去旧虚拟 DOM 中寻找是否有相同的 key,如果有那就再对比这两个元素内的其他元素是否相同,如果相同那就直接将旧虚拟 DOM 已经生成好的真实 DOM 直接拿过来用,如果不同那就由新虚拟 DOM 自己生成真实 DOM。

首先新虚拟 DOM 中 key 为0的 li 元素与旧虚拟 DOM 中 key 为0的 li 元素进行对比,发现 key 都是0,接下来对比 li 中的其他元素。在 li 中有两个元素节点,先对比第一个,发现不一样,那么此时新虚拟 DOM 就直接将该元素转换为真实 DOM。接下来再对比第二个,发现一样,那么就将旧虚拟 DOM 已经转换好的真实 DOM 拿过来用。

剩下的对比都遵循这个逻辑,依次生成真实 DOM,但是对比到最后一个发现 key 为3的在旧虚拟 DOM 中不存在,那就直接将 key 为3的 li 元素整个转换成真实 DOM,最后就出现了数据错乱的样子。

其实如果我将新数据不添加到原数据的头部,而是添加到原数据的尾部,也就不会出现数据错乱的样子。因为将新数据添加的原数据的尾部按照顺序生成虚拟 DOM 后新数据的 key 应该为3,key 为3的话旧虚拟 DOM 中没有,那就只能乖乖的将新数据的整个 li 元素转换为真实 DOM。

所以如果对原数据有破坏顺序性的操作,就要谨慎使用索引来作为 key。

将唯一标识作为key

修改<li>标签内的代码,将数据的唯一标识作为 key

<li v-for="car in cars" :key="car.id">
    {{ car.brand }}--{{car.price}}
    <input type="text">
</li>

运行程序并手动在输入框中输入内容,同时点击按钮添加新数据。

看下图中列表结构就很符合逻辑。

在将索引作为 key 的那一小节中,大篇幅的讲解了其中的原理以及 Vue 的虚拟 DOM 对比算法是如何进行对比的。所以对于上图的结果相信大家也能很好的理解。

注意看初始虚拟 DOM 与新虚拟 DOM 中元素的 key 已经是每条数据的 id 了,那么接下来就好分析了。

首先将新虚拟 DOM 中 key 为04的 li 元素到初始虚拟 DOM 中对比,发现初始虚拟 DOM 中并没有 key 为04的 li 元素,所以新虚拟 DOM 就将该元素转换成真实 DOM。

然后后边的 li 依次去初始虚拟 DOM 中对比,发现 key 都相同且相同 key 的 li 元素里的其他元素也相同,所以就将在初始虚拟 DOM 就已经转换好的真实 DOM 直接拿过来用。

可以看出使用 id 作为 key 并不会受到打乱数据顺序而产生数据显示错乱的情况。

总结

  1. 虚拟 DOM 中 key 的作用:
    1. key 是虚拟 DOM 对象的标识,当数据发生变化时,Vue 会根据【新数据】生成【新的虚拟 DOM 】, 随后 Vue 进行【新虚拟 DOM 】与【旧虚拟 DOM 】的差异比较,比较规则如下:
  2. 对比规则:
    1. 若虚拟 DOM 中内容没变, 直接使用之前的真实 DOM!
    2. 若虚拟 DOM 中内容变了, 则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM
    3. 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key:
    4. 旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key 创建新的真实 DOM,随后渲染到到页面。
  3. 用索引作为 key 可能会引发的问题:
    1. 会产生错误 DOM 更新 ==> 界面有问题。
    2. 会产生没有必要的真实 DOM 更新 ==> 界面效果没问题, 但效率低。
    3. 若对数据进行逆序添加、逆序删除等破坏顺序操作:
    4. 如果结构中还包含输入类的 DOM:
  4. 开发中如何选择 key?
    1. 最好使用每条数据的唯一标识作为 key, 比如 id、手机号、身份证号、学号等唯一值。
    2. 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用 index 作为 key 是没有问题的。

 

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值