列表渲染
v-for
遍历数组
我们可以用v-for
指令基于一个数组来渲染一个列表。v-for
指令需要使用item
形式的特殊语法,其中items是源数据数组,而item是迭代项的别名。
// js const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
// template <li v-for="itemin items"> {{ item.message }} </li>
在v-for
块中,我们可以访问所有父作用域的 property。v-for
也支持使用可选的第二个参数,表示当前项的位置索引。
const parentMessage = ref('Parent') const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
<li v-for="(item, index)in items"> {{ parentMessage }} - {{ index }} - {{ item.message }} </li>
v-for
变量的作用域和下面的 JavaScript 代码很类似:
const parentMessage = 'Parent' const items = [ /* ... */ ] items.forEach((item, index) => { // 可以访问外层的 `parentMessage` // 而 `item` 和 `index` 只在这个作用域可用 console.log(parentMessage, item.message, index) })
注意v-for
是如何对应forEach
回调的函数签名的。
实际上,你也可以在定义v-for
的变量别名时,使用解构,和解构函数参数类似:
<liv-for ="{ message }in items"> {{ message }} </li>
<!-- 有 index 索引时 --> <liv-for ="({ message}, index)in items"> {{ message }} {{ index }} </li>
对于嵌套多层的v-for
,作用域的工作方式和函数的作用域很类似。每个v-for
作用域都可以访问到父级作用域:
<liv-for ="item in items"> <spanv-for ="childItem in item.children"> {{ item.message }} {{ childItem }} </span> </li>
你也可以用of
替代in
作为分隔符,这也和 JavaScript 的迭代器语法非常相似:
<divv-for ="item of items"></div>
遍历对象
你也可以用v-for
来遍历一个对象的 property。
const myObject =reactive ({ title: '如何在 Vue 中渲染列表', author: '王小明', publishedAt: '2016-04-10' })
<ul> <liv-for ="valuein myObject"> {{ value }} </li> </ul>
你也可以提供第二个的参数为property 名称(例如key):
<li v-for="(value, key)in myObject"> {{ key }}: {{ value }} </li>
第三个参数表示位置索引:
<li v-for="(value, key, index)in myObject"> {{ index }}. {{ key }}: {{ value }} </li>
当遍历一个对象时,顺序是依据Object.keys()
的枚举顺序,由于不同的 JavaScript 引擎可能会有不同的实现,所以可能会导致顺序不一致。
遍历整数
可以直接传给v-for
一个整数值。在这种用例中,将会将该模板基于1...n
的取值范围重复多次。
<span v-for="n in 10">{{ n }}</span>
渲染结果:
<span>1</span> <span>2</span> <span>3</span> <span>4</span> <span>5</span> <span>6</span> <span>7</span> <span>8</span> <span>9</span> <span>10</span>
注意此处n的初值是从1开始而非0。
<template>上的 v-for
与模板上的v-if
类似,你也可以在<template>
标签上使用v-for
来渲染包含多个元素的一个块。例如:
<ul> <templatev-for ="item in items"> <li>{{ item.msg }}</li> <li class="divider" role="presentation"></li> </template> </ul>
v-for 与 v-if
同时使用v-if
和v-for
是不推荐的,因为这样二者的优先级不明显。
当它们同时存在于一个节点上时,v-if
比v-for
的优先级更高。这意味着v-if
的条件将无法访问到v-for
作用域内定义的变量别名:
<!-- 这会抛出一个错误,因为属性 todo 此时没有在该实例上定义 --> <li v-for="todoin todos" v-if="!todo.isComplete"> {{ todo.name }} </li>
在外新包裹一层<template>
再在其上使用v-for
可以解决这个问题(这也更加明显易读):
<template v-for="todoin todos"> <li v-if="!todo.isComplete"> {{ todo.name }} </li></template>
通过 key 管理状态
当 Vue 更新一个v-for
渲染的列表时,默认情况下使用的是“原地修补”的策略。如果数据项的顺序发生了改变,Vue 不是通过移动 DOM 元素来匹配条目的顺序,而是在适当的位置对每个元素进行修补,确保它们在原本指定的索引位置上渲染。默认模式是高效的,但只适用于列表渲染输出不依赖子组件状态或者临时 DOM 状态,例如表单输入值。
为了给 Vue 一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素,你需要为每个项目提供一个唯一的key
attribute:
<divv-for ="itemin items" :key="item.id"> <!-- content --> </div>
当你使用<template v-for>
时,key
应该被放置在这个<template>
容器上:
<template v-for ="todoin todos" :key="todo.name"> <li>{{ todo.name }}</li></template>
key
在这里是一个通过v-bind
绑定的特殊 attribute。请不要和在v-for
中使用对象里所提到的对象属性的key
相混淆了。
推荐在任何可行的时候为v-for
时提供key
attribute,除非所迭代的 DOM 内容非常简单(例如:不包含组件或有状态的 DOM 元素),或者有意依赖默认行为来获得性能增益。
key
绑定的值期望是一个基础类型的值,例如字符串或number类型。不要用对象作为v-for
的 key。
组件上使用 v-for
这一小节假设你已了解组件的相关知识,或者你也可以先跳过这里,之后再回来看。
可以直接在组件上使用v-for
,和其他任何一般的元素没有区别(别忘记提供一个key
):
<my-componentv-for ="itemin items":key ="item.id"></my-component>
但是,这不会自动将任何数据传递给组件,因为组件有自己独立的作用域。为了将迭代后的数据传递到组件中,我们还是应该使用props:
<my-componentv-for ="(item, index) in items" :item="item" :index="index":key ="item.id" ></my-component>
不自动将item注入组件的原因是,这会使组件与v-for
的工作方式紧密耦合。明确其数据的来源可以使组件在其他情况下可重用。
数组变化侦测
变更方法
变更方法,会变更原始数组。Vue 能够侦听响应式数组的变更方法,并在它们被调用时,触发相关的视图更新。这些变更方法包括:
push()
:向数组的末尾添加一个或多个元素,并返回新的长度。pop()
:删除数组的最后一个元素,并返回最后一个元素。shift()
:删除数组的第一个元素,并返回第一个元素的值。unshift()
:向数组的开头添加一个或更多元素,并返回新的长度。sort()
:对数组的元素进行排序。reverse()
:颠倒数组中元素的顺序。splice()
:向/从数组中添加/删除项目,然后返回被删除的项目。- 删除功能:array.splice(index,num)。
- index:必需。数组元素的下标,必须是数字。使用负数可从数组结尾处规定位置。
- num:可选。要删除的项目数量。如果设置为 0,则不会删除项目。如果未规定此参数,则删除从 index 开始到原数组结尾的所有元素。
- 插入功能:array.splice(index,0,item)。
- index:必需。数组元素的下标,必须是数字。使用负数可从数组结尾处规定位置。
- 0:必需是 0。表示插入功能。
- item:必需。规定要添加到数组的新元素。从 index 所指的下标处开始插入。
- 替换功能:array.splice(index,num,item)。
- index:必需。数组元素的下标,必须是数字。使用负数可从数组结尾处规定位置。
- num:必需。规定应该替换多少元素。必须是正整数。
- item:必需。规定要添加到数组的新元素。从 index 所指的下标处开始替换。
- 删除功能:array.splice(index,num)。
非变更方法
非变更方法,不变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组,触发相关的视图更新。
filter()
:创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。concat()
:用于连接两个或多个数组。slice()
:从已有的数组中返回选定的元素。split()
:用于把一个字符串分割成字符串数组。
// `items` 是一个数组的 ref items.value = items.value.filter ((item) => item.message.match(/Foo/))
你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。
展示过滤或排序后的结果
有时,我们想要显示一个数组经过过滤或排序后的版本,而不实际变更或重置原始数据。在这种情况下,可以创建一个计算属性,来返回过滤或排序后的数组。
const numbers = ref([1, 2, 3, 4, 5]) const evenNumbers = computed(() => { return numbers.value.filter((n) => n % 2 === 0) })
<li v-for="n in evenNumbers">{{ n }}</li>
在计算属性不适用的情况下(例如,在嵌套v-for
循环中)你可以使用一个方法:
const sets = ref([ [1, 2, 3, 4, 5], [6, 7, 8, 9, 10] ]) function even(numbers) { return numbers.filter((number) => number % 2 === 0) }
<ul v-for="numbers in sets"> <li v-for="n in even(numbers)">{{ n }}</li> </ul>
在计算属性中使用reverse()
和sort()
请保持谨慎!这两个方法将改变原始数组,计算函数中不应该这么做。请在调用这些方法之前创建一个原数组的副本:
// 慎用: return numbers.reverse() // 推荐: return [...numbers].reverse()