Vue 列表渲染
1. 列表渲染基础
1.1 什么是列表渲染
列表渲染是一种将一个数组、字符串、对象、数字以一定的方式展开成多个项目渲染到页面上的方式。
1.2 基本用法举例
1.2.1 数组
<ol> <li v-for="(item, index) in ['A', 'B', 'C']">item = {{ item }}, and index = {{ index }}</li> </ol>
其渲染效果为:
注意:
- 使用
v-for ... in
和v-for ... of
都可以,它们没有区别。(后文不在赘述)
1.2.2 数字
v-for
可以直接接受一个整数值,这相当于接受一个从 1 开始到该整数的数组。例如:
<ol> <li v-for="(number, index) in 3">number = {{number}}, and index = {{index}}</li> </ol>
其渲染效果为:
注意:
- index 和 所遍历项的数字在值上看,总是index比遍历项少1,因为 index 是从 0 开始计算而遍历项是从1开始算的。
1.2.3 字符串
v-for
指令可以遍历一个字符串。例如:
<ol> <li v-for="(item, index) of str">item = {{ item }}, and index = {{ index }}</li> </ol>
其渲染效果为:
1.2.4 对象
v-for
指令在遍历一个对象时,将使用 JavaScript 对象的 属性名(键)作为遍历时的 index,使用 与该 属性名 对应的 值 作为遍历项,例如:
<ol> <li v-for="(item, index) of { J: 'ack', B: 'ush', A: 'lice' }">index = {{ index }}, and item = {{ item }}</li> </ol>
其渲染效果为:
1.2.5 集合(Set)
集合(Set)是 ECMA Script 6 中定义的一个存储任何类型的唯一值的类型,它最常用于对一个数组进行去重。比如:
const s = new Set(["张三", "李四", "王五", "李四", "王五"]) console.log(s); console.log([...s]);
Out[]:
{ "Set(3)": [ "张三", "李四", "王五" ] } [ "张三", "李四", "王五" ]
使用 v-for
遍历一个集合的例子如下:
const s = new Set(["张三", "李四", "王五", "李四", "王五"])
<ol> <li v-for="(item, index) of s">item = {{ item }}, and index = {{ index }}</li> </ol>
其渲染效果为:
1.2.6 映射(Map)
提示: 在编程语言中的 Map 含义不是地图,而是映射,这也是该单词最原始的含义。
映射(Map)是 ECMA Script 6 中定义的一个用于存储键值对的类型,任何值(对象或者基本类型)都可以作为一个键或一个值。虽映射像对象那样,具有键值对的使用形式,使用 v-for
遍历一个 映射 与遍历一个数组的不同之处在于,遍历映射时将以从 0 开始的整数索引值作为 index,使用 [key, word]
形式的数组作为遍历项。例如:
const m = new Map() m.set('张', '三'); m.set('李', '四'); m.set('王', '五');
<ol> <li v-for="(item, index) of m">item = {{ item }}, and index = {{ index }}</li> </ol>
其渲染效果为:
2. 数组渲染进阶
2.1 列表渲染中的 key 属性
2.1.1 key 属性于虚拟DOM 的更新规则
key 是 vue 中虚拟 DOM 所使用的对象标识,当数据发生变化时,Vue 会根据 新数据 生成 新的 虚拟 DOM,然后对从上到下从外到内从左到右对虚拟DOM进行扫描,对于某一个虚拟DOM,新的虚拟 DOM 与 旧的虚拟 DOM 进行差异比较。比较方式为:
- 先比较新旧虚拟 DOM 的 key 属性:
- 如果新的虚拟DOM中key 不存在与 旧的虚拟DOM中,则:
将该新的虚拟 DOM 更新到内存做为新的虚拟DOM,同时渲染到页面为新的真实DOM。 - 如果新的虚拟DOM的 key 属性值存在于 旧的虚拟DOM 中,则:从前到后比较该虚拟DOM的所有内容(子元素)。
- 如果内容没变,则:直接使用旧的虚拟DOM。
- 如果内容改变,则在内容改变处更新虚拟DOM,将将其渲染为新的真实DOM。
- 对于内容的内容依次方式递归处理。
2.1.2 省略 key 时可能出现的问题
很多时候的确不写 key 属性也能实现我们需要的效果,比如:
const data = ["尔康", "尔泰", "紫薇", "小燕子"];
<ul> <li v-for="(item, index) in data">{{ item }}</li> </ul>
虽然我们没有指定key
属性,但是效果依然可以正常渲染:
然而
2.1.3 使用 index 作为 key 可能出现的问题
多数情况下我们的确可以直接使用 index 作为 v-for 遍历所需的 key 使用,例如给定一个三级手风琴目录数据如下:
const menu = [ { title: "一级目录1", childern: [ { title: "1-1" }, { title: "1-2" }, ] }, { title: "一级目录2" }, { title: "一级目录3", childern: [ { title: "3-1", childern: [ { title: "3-1-1" }, { title: "3-1-2" }, ] }, { title: "3-2" }, ] } ]
我们对数据逐层进行展开:
<ul> <li v-for="(menu1, index1) in menu" :key="index1"> <p>{{ menu1.title }}</p> <ul v-if="menu1.childern"> <li v-for="(menu2, index2) in menu1.childern" :key="index2"> <p>{{ menu2.title }}</p> <ul v-if="menu2.childern"> <li v-for="(menu3, index3) in menu2.childern" :key="index3"> <p>{{ menu3.title }}</p> </li> </ul> </li> </ul> </li> </ul>
其渲染效果为:
在 2.1.1 节中我们知道
2.1.4 开发中如何选择 key
在上面,我们讨论了两种和虚拟DOM有关的问题:
- 若列表渲染中存在逆序增减项目等类似 破环原先渲染项顺序 的更新行为:
将产生没有必要的真实DOM更新,降低页面渲染效率。 - 若列表渲染中,渲染项还包含了表单。在破环顺序的曾减项时:
将导致表单错位等意外情形发生。
在 Vue 中,key
属性是用于给 vue 框架标识虚拟DOM用的。为了避免上述非预期情况的发生,我们应该 使用能够唯一标识每条数据的值作为数据项们的 key 属性值。通过这种方式:
- 对于逆序增减等破坏顺序的操作:
由于 key 属性标明了各条数据的唯一标识,Vue 能够正确识别后面没有改动的DOM,不会对这些不需要改动的 虚拟DOM 对于更新渲染到 真实DOM。 - 对于不仅破坏顺序,而且存在表单的情形:
由于Vue能够正确唯一识别每一条 虚拟DOM 项,一旦虚拟DOM变化,Vue不会去检查包含表单子项的改动。这些表单往往又是有状态的,即当用户在真实DOM的表单上改变了这些表单值时,虚拟DOM却不会改变,Vue也不会知道这些变动。
通过人为地添加每项地唯一标识 key,Vue不再需要检查子项内容而错误更新,直接更新破坏顺序增减地整项,就避免了这种更新错乱地行为发生。
因此 找到能够唯一标识每条数据的值作为数据项们的 key 属性值 就成了提高渲染效率、避免更新错误地关键。那么采用什么作为列表渲染中数据项地 key 呢?
一般在实际开发中,合格地后端都需要给前端返回一个每条数据的唯一标识,这个标识往往能在 数据库 中唯一的确定该条数据,这样不论什么时候从后端请求跟新的新数据都能够唯一识别这条数据。比如,在一个标识用户信息的数据表中,昵称往往是重复的,用户id是唯一的,那么就可以使用用户id作为key。