列表渲染
v-for
我们可以使用v-for
指令根据数组渲染一个项目列表。`v-for`指令需要特殊语法,形式为`item in items`,其中`items`是源数据数组,`item`是正在迭代的数组元素的别名
js
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
template
<li v-for="item in items">
{{ item.message }}
</li>
在`v-for`的作用域内,模板表达式可以访问所有父作用域属性。此外,`v-for`还支持可选的第二个别名,用于当前项的索引
js
const parentMessage = ref('Parent')
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
template
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
`v-for`的变量作用域类似于以下JavaScript
js
const parentMessage = 'Parent'
const items = [
/* ... */
]
items.forEach((item, index) => {
// has access to outer scope `parentMessage`
// but `item` and `index` are only available in here
console.log(parentMessage, item.message, index)
})
注意`v-for`的值如何与`forEach`回调函数的函数签名匹配。实际上,你可以在`v-for`项别名上使用类似解构函数参数的解构
template
<li v-for="{ message } in items">
{{ message }}
</li>
<!-- with index alias -->
<li v-for="({ message }, index) in items">
{{ message }} {{ index }}
</li>
嵌套`v-for`的作用域也类似于嵌套函数。每个`v-for`作用域都可以访问父作用域
template
<li v-for="item in items">
<span v-for="childItem in item.children">
{{ item.message }} {{ childItem }}
</span>
</li>
你还可以使用`of`作为分隔符而不是`in`,这样它就更接近JavaScript的迭代器语法
template
<div v-for="item of items"></div>
v-for
与对象
您还可以使用 v-for
来遍历对象的属性。遍历顺序将基于在对象上调用 Object.values()
的结果
js
const myObject = reactive({
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
})
template
<ul>
<li v-for="value in myObject">
{{ value }}
</li>
</ul>
您还可以提供第二个别名来表示属性名(即键)
template
<li v-for="(value, key) in myObject">
{{ key }}: {{ value }}
</li>
另一个用于索引
template
<li v-for="(value, key, index) in myObject">
{{ index }}. {{ key }}: {{ value }}
</li>
v-for
与范围
v-for
也可以接受一个整数。在这种情况下,它将根据范围 1...n
重复模板多次。
template
<span v-for="n in 10">{{ n }}</span>
注意这里 n
的初始值为 1
,而不是 0
。
v-for
在 <template>
上
与模板 v-if
类似,您也可以使用带有 v-for
的 <template>
标签来渲染多个元素的块。例如
template
<ul>
<template v-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
范围的变量
template
<!--
This will throw an error because property "todo"
is not defined on instance.
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
</li>
可以通过将 v-for
移动到包装的 <template>
标签来解决此问题(这也更明确)
template
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
使用 key
维护状态
当 Vue 更新使用 v-for
渲染的元素列表时,默认情况下它使用“原地修补”策略。如果数据项的顺序已更改,Vue 不会移动 DOM 元素以匹配项目的顺序,而是会原地修补每个元素并确保它反映了特定索引应该渲染的内容。
此默认模式是高效的,但**仅适用于您的列表渲染输出不依赖于子组件状态或临时 DOM 状态(例如表单输入值)的情况**。
要给 Vue 提供提示,使其能够跟踪每个节点的身份,从而重用和重新排序现有元素,您需要为每个项目提供唯一的 key
属性
template
<div v-for="item in items" :key="item.id">
<!-- content -->
</div>
当使用 <template v-for>
时,key
应放置在 <template>
容器上
template
<template v-for="todo in todos" :key="todo.name">
<li>{{ todo.name }}</li>
</template>
注意
这里的 key
是一个与 v-bind
绑定的特殊属性。当使用 v-for
与对象 时,不要将其与属性键变量混淆。
建议在可能的情况下为 v-for
提供一个 key
属性,除非迭代 DOM 内容很简单(即不包含组件或具有状态性的 DOM 元素),或者您有意依赖默认行为以获得性能提升。
key
绑定期望原始值 - 即字符串和数字。不要将对象用作 v-for
键。有关 key
属性的详细使用方法,请参阅 key
API 文档。
v-for
与组件
本节假设您已了解 组件。您可以随时跳过此部分,稍后再回来。
您可以直接在组件上使用 v-for
,就像任何普通元素一样(别忘了提供 key
)。
template
<MyComponent v-for="item in items" :key="item.id" />
但是,这不会自动将任何数据传递给组件,因为组件有自己的独立作用域。为了将迭代的传给组件,我们还应该使用 props。
template
<MyComponent
v-for="(item, index) in items"
:item="item"
:index="index"
:key="item.id"
/>
不自动将 item
注入组件的原因是因为这会使组件与 v-for
的工作方式紧密耦合。明确其数据来源使组件可以在其他情况下重用。
查看 这个简单的待办事项列表示例,了解如何使用 v-for
渲染组件列表,并为每个实例传递不同的数据。
数组变更检测
变异方法
Vue 能够检测到调用响应式数组的变异方法,并触发必要的更新。这些变异方法包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
替换数组
变异方法,顾名思义,会改变它们被调用的原始数组。相比之下,也存在非变异方法,例如 filter()
、concat()
和 slice()
,它们不会改变原始数组,但总是返回一个新数组。当使用非变异方法时,我们应该用新数组替换旧数组。
js
// `items` is a ref with array value
items.value = items.value.filter((item) => item.message.match(/Foo/))
您可能认为这将导致 Vue 丢弃现有的 DOM 并重新渲染整个列表——幸运的是,并非如此。Vue 实现了一些智能启发式方法来最大化 DOM 元素的复用,因此用包含重叠对象的另一个数组替换数组是一个非常高效的操作。
显示过滤/排序结果
有时我们想显示数组的过滤或排序版本,而不实际修改或重置原始数据。在这种情况下,您可以创建一个计算属性,该属性返回过滤或排序后的数组。
例如
js
const numbers = ref([1, 2, 3, 4, 5])
const evenNumbers = computed(() => {
return numbers.value.filter((n) => n % 2 === 0)
})
template
<li v-for="n in evenNumbers">{{ n }}</li>
在计算属性不可行的情况下(例如,在嵌套 v-for
循环中),您可以使用一个方法
js
const sets = ref([
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10]
])
function even(numbers) {
return numbers.filter((number) => number % 2 === 0)
}
template
<ul v-for="numbers in sets">
<li v-for="n in even(numbers)">{{ n }}</li>
</ul>
小心使用计算属性中的 reverse()
和 sort()
!这两个方法会修改原始数组,这应该避免在计算属性中。在这些方法之前创建原始数组的副本。
diff
- return numbers.reverse()
+ return [...numbers].reverse()