【vue回顾系列】10-彻底搞清楚v-for为什么要写key这个唯一标识符

前言

适合已经会使用vue,但从来没去了解key的作用是什么的小伙伴观看。

建议先看【vue回顾系列】03-什么是模板编译,vdom以及它的更新机制是怎么样的,了解diff算法。


key的作用

我们举个例子:

<ul>
     <li v-for="(item, index) in list" :key="index">
         {{ item.name }}
     </li>
 </ul>
data() {
    return {
        list: [
            { id: 1, name: 'a' },
            { id: 2, name: 'b' },
            { id: 3, name: 'c' },
            { id: 4, name: 'd' }
        ]
    }
}

我们给每个li标签加入index下标作为key值,渲染出来的页面是:
在这里插入图片描述

vue的视图渲染过程是,先进行模板编译,结合数据,然后在内存中计算出虚拟DOM,最后挂载到真实页面上。这时我们手动插一条数据{ id: 5, name: 'e' }到list尾部,那么vue会重新完整的计算一个新的虚拟DOM放到内存中,然后进行新旧虚拟DOM的对比:

// 旧(虚拟dom实际不是长这个样,方便理解就这样写了)
[
    <li>a</li>,  // key 0
    <li>b</li>,  // 1
    <li>c</li>,  // 2
    <li>d</li>   // 3
]
// 新
[
    <li>a</li>,  // 0
    <li>b</li>,  // 1
    <li>c</li>,  // 2
    <li>d</li>,  // 3
    <li>e</li>   // 4
]

vue根据最新虚拟DOM的每个key值去与旧虚拟DOM做对比,先从新虚拟DOM中第一个,也就是key为0开始,是个<li>a</li>,然后它发现旧的0是个<li>a</li>,俩哥们长得一模一样,那就不挂载到真实页面上了,减少性能上的开支。以此类推到key为4的时候,vue发现旧虚拟DOM上没有<li>e</li>,那就需要渲染挂到真实页面上了。

我们从上述过程中可以看到,新增的一条数据,因为有key的帮助,不会使得整个页面全部重新渲染,只渲染出不同的那个。

当页面数据量庞杂且常动态变化的时候,key的作用就能大大提高页面的渲染效率。这个对比的过程也称为diff算法


key应为唯一标识符

性能损失

如果在一些数据会变动的DOM中,我们最好不要将key写为index索引值,我们来看上面的改造例子,把向后插入数据改为向前插入数据:

<ul>
	 <button @click="addE">向前添加e</button>
     <li v-for="(item, index) in list" :key="index">
         {{ item.name }}
     </li>
 </ul>
data() {
    return {
        list: [
            { id: 1, name: 'a' },
            { id: 2, name: 'b' },
            { id: 3, name: 'c' },
            { id: 4, name: 'd' }
        ]
    }
}
methods: {
    addE() {
        this.list.unshift({ id: 5, name: 'e' })
    }
}

当我们点击按钮向前添加一条数据时,我们再来看看新旧虚拟DOM的对比:

// 旧(虚拟dom实际不是长这个样,理解一下就好)
[
    <li>a</li>,  // key 0
    <li>b</li>,  // 1
    <li>c</li>,  // 2
    <li>d</li>   // 3
]
// 新
[
	<li>e</li>   // 0
    <li>a</li>,  // 1
    <li>b</li>,  // 2
    <li>c</li>,  // 3
    <li>d</li>,  // 4
]

你会发现从key为0开始,每个新虚拟DOM都对不上旧的了,这时vue就会认为新虚拟DOM和旧的完全不一致,那么就把新的全部渲染挂载到真实页面上。但其实只有一条数据是新的,浪费了性能。

diff算法导致的显示错误

之前可以看到了,在做新旧虚拟DOM对比的时候,是一个标签一个标签的进行对比,也就是diff算法的最小颗粒度为一个标签

我们再来点补充,把上面例子的每个li标签中写上一个input标签,并在b标签输入内容后点击添加数据按钮时,会发现:
在这里插入图片描述
b的输入框内容跑到a去了,王德发!这是因为:

diff算法的最小颗粒度虽然是一个标签,但是如果标签内部还有标签,那么diff会继续向内比较下去,也就是说可以换掉这一层的标签的同时保持内层标签的不变。
具体可看【vue回顾系列】03-什么是模板编译,vdom以及它的更新机制是怎么样的

以下只是我个人猜想,供参考:

在上面这个例子中,a、b、c、d这一层的节点,因为e的加入a、b、c、d这层DOM节点重新渲染,而他们下面的input层的节点,并不会发生改变,仍然用着key为下标的值去找父节点连接上,所以就发生了连接错位的问题。


key怎么选

一般情况下,如果数组的每个子项中有id属性这样的唯一值,就使用它即可。没有推荐两个办法:

  • 第三方轻量库,nanoid。
  • 浏览器原生对象crypto.randomUUID(),用的时候看看浏览器支持情况

总结

key如果使用index作为值会导致:

  • 第一,当进行逆序添加、逆序删除等破坏顺序操作时,会重新渲染,导致性能效率低的问题;
  • 第二,容易照成显示错位的问题出现;

如果我们给key指定了唯一的标识符,也就是说给每个数据指定一个身份证一样的东西,那么diff每次就只会通过身份证去认人,就不会出现以上问题,例如用数据中的id字段:

<ul>
	 <button @click="addE">向前添加e</button>
     <li v-for="item in list" :key="item.id">
         {{ item.name }}
     </li>
 </ul>
```javascript
// 旧(虚拟dom实际不是长这个样,理解一下就好)
[
    <li>a</li>,  // key 1
    <li>b</li>,  // 2
    <li>c</li>,  // 3
    <li>d</li>   // 4
]
// 新
[
	<li>e</li>   // 5
    <li>a</li>,  // 1
    <li>b</li>,  // 2
    <li>c</li>,  // 3
    <li>d</li>,  // 4
]

这样在新虚拟DOM中key为1-4的都能在旧虚拟DOM中找到对应正确的数据,只会去渲染key为5的,挂载到真实页面上。

当然,如果数据只是展示不操作,那么用index为key也是可以的,不影响。


题外话 v-if一起用的情况

在vue2中,当它们处于同一节点,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。

然而在vue3中反过来了

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值