Close
升级到 Vue 3 | Vue 2 EOL

列表渲染

使用 v-for 将数组映射到元素

我们可以使用 v-for 指令根据数组渲染项目列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,item 是正在迭代的数组元素的 **别名**

<ul id="example-1">
<li v-for="item in items" :key="item.message">
{{ item.message }}
</li>
</ul>
var example1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})

结果

v-for 块内,我们可以完全访问父作用域属性。v-for 还支持可选的第二个参数来表示当前项目的索引。

<ul id="example-2">
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
var example2 = new Vue({
el: '#example-2',
data: {
parentMessage: 'Parent',
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})

结果

你也可以使用 of 作为分隔符而不是 in,使其更接近 JavaScript 的迭代器语法

<div v-for="item of items"></div>

使用对象的 v-for

你也可以使用 v-for 迭代对象的属性。

<ul id="v-for-object" class="demo">
<li v-for="value in object">
{{ value }}
</li>
</ul>
new Vue({
el: '#v-for-object',
data: {
object: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
})

结果

你也可以提供第二个参数来表示属性的名称(也称为键)

<div v-for="(value, name) in object">
{{ name }}: {{ value }}
</div>
{{ name }}: {{ value }}

以及另一个参数来表示索引

<div v-for="(value, name, index) in object">
{{ index }}. {{ name }}: {{ value }}
</div>
{{ index }}. {{ name }}: {{ value }}

迭代对象时,顺序基于 Object.keys() 的枚举顺序,这 **不** 保证在不同的 JavaScript 引擎实现中保持一致。

维护状态

当 Vue 更新使用 v-for 渲染的元素列表时,默认情况下它使用“就地修补”策略。如果数据项的顺序发生了变化,Vue 不会移动 DOM 元素以匹配项的顺序,而是会就地修补每个元素并确保它反映了该特定索引处应该渲染的内容。这类似于 Vue 1.x 中 track-by="$index" 的行为。

这种默认模式效率很高,但 **仅适用于列表渲染输出不依赖于子组件状态或临时 DOM 状态(例如表单输入值)的情况**。

为了给 Vue 一个提示,以便它可以跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每个项目提供一个唯一的 key 属性

<div v-for="item in items" v-bind:key="item.id">
<!-- content -->
</div>

建议在可能的情况下为 v-for 提供 key 属性,除非迭代的 DOM 内容很简单,或者你故意依赖默认行为以提高性能。

由于它是一种通用的机制,用于 Vue 识别节点,因此 key 还有其他用途,这些用途并不专门与 v-for 相关,我们将在本指南的后面部分看到。

不要使用对象和数组等非原始值作为 v-for 键。请改用字符串或数字值。

有关 key 属性的详细用法,请参阅 key API 文档

数组变更检测

变异方法

Vue 包装了被观察数组的变异方法,以便它们也会触发视图更新。包装的方法是

你可以在控制台中打开并使用前面的示例的 items 数组,通过调用它们的变异方法。例如:example1.items.push({ message: 'Baz' })

替换数组

变异方法顾名思义,会改变它们被调用的原始数组。相比之下,还有一些非变异方法,例如 filter()concat()slice(),它们不会改变原始数组,而是 **始终返回一个新数组**。在使用非变异方法时,你可以用新数组替换旧数组

example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})

你可能会认为这会导致 Vue 丢弃现有的 DOM 并重新渲染整个列表 - 幸运的是,情况并非如此。Vue 实现了一些智能启发式方法,以最大限度地重用 DOM 元素,因此用包含重叠对象的另一个数组替换数组是一个非常高效的操作。

注意事项

由于 JavaScript 的限制,Vue **无法检测** 数组和对象中的某些类型的更改。这些在 响应性 部分中讨论。

显示过滤/排序的结果

有时我们希望显示数组的过滤或排序版本,而实际上并不改变或重置原始数据。在这种情况下,你可以创建一个计算属性,它返回过滤或排序后的数组。

例如

<li v-for="n in evenNumbers">{{ n }}</li>
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
evenNumbers: function () {
return this.numbers.filter(function (number) {
return number % 2 === 0
})
}
}

在计算属性不可行的情况下(例如在嵌套的 v-for 循环中),你可以使用方法

<ul v-for="set in sets">
<li v-for="n in even(set)">{{ n }}</li>
</ul>
data: {
sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}

使用范围的 v-for

v-for 也可以接受一个整数。在这种情况下,它会重复该模板指定的次数。

<div>
<span v-for="n in 10">{{ n }} </span>
</div>

结果

{{ n }}

<template> 上使用 v-for

类似于模板 v-if,你也可以使用 <template> 标签与 v-for 一起渲染多个元素的块。例如

<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>

v-forv-if

请注意,**不** 建议将 v-ifv-for 放在一起使用。有关详细信息,请参阅 风格指南

当它们存在于同一个节点上时,v-for 的优先级高于 v-if。这意味着 v-if 将在循环的每次迭代中分别运行。这在你想仅为某些项目渲染节点时很有用,如下所示

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

上面的代码只渲染未完成的任务。

如果你的意图是条件性地跳过循环的执行,你可以将 v-if 放在包装元素(或 <template>)上。例如

<ul v-if="todos.length">
<li v-for="todo in todos">
{{ todo }}
</li>
</ul>
<p v-else>No todos left!</p>

v-for 与组件

本节假设你了解 组件。你可以跳过它,稍后再回来。

你可以在自定义组件上直接使用 v-for,就像任何普通元素一样

<my-component v-for="item in items" :key="item.id"></my-component>

在 2.2.0+ 版本中,当使用 v-for 与组件一起使用时,现在需要 key

但是,这不会自动将任何数据传递给组件,因为组件有自己的隔离作用域。为了将迭代数据传递到组件中,我们也应该使用 props

<my-component
v-for="(item, index) in items"
v-bind:item="item"
v-bind:index="index"
v-bind:key="item.id"
></my-component>

不自动将 item 注入组件的原因是,这会使组件与 v-for 的工作方式紧密耦合。明确地说明数据来自哪里,使组件在其他情况下可重用。

这是一个简单的待办事项列表的完整示例

<div id="todo-list-example">
<form v-on:submit.prevent="addNewTodo">
<label for="new-todo">Add a todo</label>
<input
v-model="newTodoText"
id="new-todo"
placeholder="E.g. Feed the cat"
>
<button>Add</button>
</form>
<ul>
<li
is="todo-item"
v-for="(todo, index) in todos"
v-bind:key="todo.id"
v-bind:title="todo.title"
v-on:remove="todos.splice(index, 1)"
></li>
</ul>
</div>

请注意 is="todo-item" 属性。这在 DOM 模板中是必要的,因为只有 <li> 元素在 <ul> 中有效。它与 <todo-item> 做同样的事情,但可以解决潜在的浏览器解析错误。有关详细信息,请参阅 DOM 模板解析注意事项

Vue.component('todo-item', {
template: '\
<li>\
{{ title }}\
<button v-on:click="$emit(\'remove\')">Remove</button>\
</li>\
',
props: ['title']
})

new Vue({
el: '#todo-list-example',
data: {
newTodoText: '',
todos: [
{
id: 1,
title: 'Do the dishes',
},
{
id: 2,
title: 'Take out the trash',
},
{
id: 3,
title: 'Mow the lawn'
}
],
nextTodoId: 4
},
methods: {
addNewTodo: function () {
this.todos.push({
id: this.nextTodoId++,
title: this.newTodoText
})
this.newTodoText = ''
}
}
})