浅析vue中v-for的使用技巧
2024-12-09 11:13:16

v-for是vue中最常用的指令,也是面试题中问到最多的,这里干脆都整理一下。

正文

本篇并不算太麻烦,只是简单的整理一下即可。

v-for与v-if优先级

因为现在有vue3了,所以这里回答会和vue使用版本的不同有关。

在vue2中,v-for的优先级高于v-if
在vue3中,v-if的优先级高于v-for

vue2

当它们处于同一节点,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。当你只想为部分项渲染节点时,这种优先级的机制会十分有用,如下:

1
2
3
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo }}
</li>

上面的代码将只渲染未完成的 todo。

而如果你的目的是有条件地跳过循环的执行,那么可以将 v-if 置于外层元素 (或 [`) 上。如:

1
2
3
4
5
6
<ul v-if="todos.length">
<li v-for="todo in todos">
{{ todo }}
</li>
</ul>
<p v-else>No todos left!</p>

vue3

当它们同时存在于一个节点上时,v-ifv-for 的优先级更高。

这意味着 v-if 的条件将无法访问到 v-for 作用域内定义的变量别名:

1
2
3
4
5
6
7
<!--
这会抛出一个错误,因为属性 todo 此时
没有在该实例上定义
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
</li>

在外先包装一层 <template> 再在其上使用 v-for 可以解决这个问题 (这也更加明显易读)

1
2
3
4
5
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>

v-for与v-if不推荐混用

当面试官问到你为什么v-for与v-if不推荐混用的时候,按照下边的说法去回答就可以。

节省性能,v-for与v-if混用,会导致每次渲染的时候都要进行条件判断,进而影响页面渲染的性能。

当 Vue 处理指令时,v-forv-if 具有更高的优先级,如果把 v-forv-if写在同一元素上,举例。

1
2
3
4
5
6
7
8
9
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
</ul>

将会经过如下运算:

1
2
3
4
5
this.users.map(function (user) {
if (user.isActive) {
return user.name
}
})

假设 users 数组有 10000 项,但只有一个 user 是 isActive,也会遍历整个 users 数组。

这样对性能是极不友好的。

通过将其更换为在如下的一个计算属性上遍历:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<ul>
<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>

computed: {
activeUsers: function () {
return this.users.filter(function (user) {
return user.isActive
})
}
}

我们将会获得如下好处:

  • 过滤后的列表会在 users 数组发生相关变化时才被重新运算,过滤更高效。
  • 使用 v-for="user in activeUsers" 之后,我们在渲染的时候遍历活跃用户,渲染更高效。
  • 解耦渲染层的逻辑,可维护性 (对逻辑的更改和扩展) 更强。

为了获得同样的好处,我们也可以把:

1
2
3
4
5
6
7
8
9
<ul>
<li
v-for="user in users"
v-if="shouldShowUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>

更新为:

1
2
3
4
5
6
7
8
<ul v-if="shouldShowUsers">
<li
v-for="user in users"
:key="user.id"
>
{{ user.name }}
</li>
</ul>

通过将 v-if 移动到容器元素,我们不会再对列表中的每个用户检查 shouldShowUsers

取而代之的是,我们只检查它一次,且不会在 shouldShowUsers 为否的时候运算 v-for

v-for为什么要有key

这里,我们首先附上官方的说法。

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

在没有 key 的情况下,Vue 将使用一种最小化元素移动的算法,并尽可能地就地更新/复用相同类型的元素。

如果传了 key,则将根据 key 的变化顺序来重新排列元素,并且将始终移除/销毁 key 已经不存在的元素。

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

按照这套说法,key是唯一标识,如果你操作数组中的元素,没有key来进行唯一标识,就会出现渲染混乱的情况。

key 在这里是一个通过 v-bind 绑定的特殊 attribute。请不要和在 v-for 中使用对象里所提到的对象属性名相混淆。

以前有人反驳我,说即便是不用key,v-for还是会正常渲染,只是代码提示报错。

这里,我们附上一个小例子来反驳这类说法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<template>
<div id="app">
<div>
<input type="text" v-model="name" />
<button @click="add">添加</button>
</div>
<ul>
<li v-for="(item, i) in list">
<input type="checkbox" /> {{ item.name }}
</li>
</ul>
</div>
</template>

<script>
export default {
data() {
return {
name: "",
newId: 3,
list: [
{ id: 1, name: "李斯" },
{ id: 2, name: "吕不韦" },
{ id: 3, name: "嬴政" },
],
};
},
methods: {
add() {
// 注意这里是unshift
this.list.unshift({ id: ++this.newId, name: this.name });
this.name = "";
},
},
};
</script>
  1. vue中列表循环需要加:key='唯一标识',唯一标识尽量是id,目的是为了高效地更新虚拟DOM
  2. key主要用于dom diff算法,diff算法为同级比较,比较当前标签上的key还有他当前的标签名,如果key和标签名都一样时只移动,不会重新创建元素和删除元素
  3. 没有key地时候默认使用就地复用策略。如果数据的顺序被改变,vue不是移动DOM元素来匹配数据项的改变,而是简单复用原来位置的每个元素,在进行比较时发现标签一样值不一样时,就会复用之前的位置,将新值直接放到该位置,以此类推,最后多出一个就会把最后一个删除掉。

v-for不推荐index作为key

尽量不要使用索引值index作key值,一定要用唯一标识的值,如id等。

这在很多前端开发人员眼中是常识,但很少有人深究,若是细究不用index作为key的原因,其实很简单。

数组中,index作为数组中元素的唯一标识,在没有数据改变的情况下,用来当做v-for渲染的key其实是没有问题。

若是数据有所改变,此时仍用索引index为key,当向数组中指定位置插入一个新元素后,因为这时候会重新更新index索引,对应着后面的虚拟DOM的key值全部更新了,这个时候还是会做不必要的更新,就像没有加key一样,因此index虽然能够解决key不冲突的问题,但是并不能解决复用的情况。

结语

v-for这篇本质上其实牵涉到了diff算法等vue渲染的底层原理,但本篇内容长度,就不深入细说。

后续会专门就渲染和diff算法专门出一篇文档。

参考

详解v-for中:key属性的作用 - 掘金 (juejin.cn)

vue2、vue3中的v-if和v-for的优先级问题 - 掘金 (juejin.cn)

vue2列表渲染 — Vue.js (vuejs.org)

vue3列表渲染 | Vue.js (vuejs.org)