列表渲染
v-for
我们可以使用 v-for 指令基于一个数组渲染一个列表。这个指令使用特殊的语法,形式为 item in items,items 是数据数组,item 是当前数组元素的别名:
基本用法
<ul id="example-1">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
var example1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
在线DEMO
在v-for块内部,我们可以完全访问父级作用域的属性,并且支持可选的第二个参数来标记当前元素的索引值(Vue2.x的更新,原有的$index已经移除)。
<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' }
]
}
})
在线DEMO
你也可以使用of来代替定界符in,这样看起来更像ES6提供的迭代器语法.
<div v-for="item of items"></div>
Template v-for
类似于 template v-if,也可以将 v-for 用在 <template>
标签上,以渲染一个包含多个元素的块,最终渲染的结果中并不会包含template标签。例如:
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider"></li>
</template>
</ul>
Object v-for
你也可用使用v-for来迭代(遍历)一个对象的属性.
<ul id="repeat-object" class="demo">
<li v-for="value in object">
{{ value }}
</li>
</ul>
new Vue({
el: '#repeat-object',
data: {
object: {
FirstName: 'John',
LastName: 'Doe',
Age: 30
}
}
})
在线DEMO
同样的我们可以添加第二个参数来标识这些key:
<div v-for="(value, key) in object">
{{ key }} : {{ value }}
</div>
再加上index参数:
<div v-for="(value, key, index) in object">
{{ index }}. {{ key }} : {{ value }}
</div>
注意:在遍历对象时,是按 Object.keys() 的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下是一致的。
Range v-for(值域)
v-for也可以处理一个整数,在这种情况下模板会循环多次。
<div id="repeat-n">
<span v-for="n in 10">{{ n }}</span>
</div>
new Vue({
el: '#repeat-n'
})
在线DEMO
Components and v-for
我们也可以循环一个组件(组件概念会在后面篇章中介绍),你可以像其它普通元素一样直接在一个自定义组件上使用v-for指令.
<my-component v-for="item in items"></my-component>
不过,这并不会自动的传递任何数据给组件,因为组件有它自己独立的作用域,所以为了传递迭代数据给组件,我们需要一点点额外步骤(借助props)
<my-component
v-for="(item, index) in items"
v-bind:item="item"
v-bind:index="index">
</my-component>
其实不将数据(上面例子中的item)自动注入到组件中是有它的理由的,因为那样的话会造成组件和v-for的强耦合,明确了数据来源后会让组件在其它场景中更好的被复用.
下面是一个简单的todo list的完整示例:
<div id="todo-list-example">
<input
v-model="newTodoText"
v-on:keyup.enter="addNewTodo"
placeholder="Add a todo"
>
<ul>
<li
is="todo-item"
v-for="(todo, index) in todos"
v-bind:title="todo"
v-on:remove="todos.splice(index, 1)"
></li>
</ul>
</div>
Vue.component('todo-item', {
template: '\
<li>\
{{ title }}\
<button v-on:click="$emit(\'remove\')">X</button>\
</li>\
',
props: ['title']
})
new Vue({
el: '#todo-list-example',
data: {
newTodoText: '',
todos: [
'Do the dishes',
'Take out the trash',
'Mow the lawn'
]
},
methods: {
addNewTodo: function () {
this.todos.push(this.newTodoText)
this.newTodoText = ''
}
}
})
注意: 例子中使用了is指令来标识li用自定义组件'todo-item'来解析渲染 在线DEMO
key
当VUE更新一个利用v-for指令渲染的元素列表时,vue默认采用的是"原位更新"(in-place patch)的策略. 如果列表的数据项发生改变,vue并不会通过移除DOM元素来重新匹配列表数据项的顺序,而是简单的保持原位刷新(更新索引值)并且确保映射了以唯一索引值来进行渲染。这个和Vue 1.x中的track-by="$index"
的形为类似。
这样的默认处理策略是高效的,不过只适用于你的列表渲染不依赖于子组件的状态或者临时DOM的状态(e.g input表单输入)
😀当然也可以自定义策略,我们需要给Vue一点提示,这样Vue才能懂你的心思,我们需要为每一个列表项提供一个唯一的key属性,比如id便是一个理想的值,因为一般来说id是唯一的嘛。
这个特殊的key属性和Vue 1.x中的track-by="$index"
大致相同,不过它既然是属性,就得以属性的方式作业,所以我们得使用v-bind指令来绑定变量(😅嫌v-bind啰嗦可以使用简写):
<div v-for="item in items" :key="item.id">
<!-- content -->
</div>
官方推荐无论何时都需要提供一个key属性,除非数据项比较简单不会发生重排的情况,或者你确实就需要默认策略的那种效果来展示.
由于这是Vue用来标识唯一结点的通用机制,key还有很多其它用途😄日后再说。
数组变化检测(Array Change Detection)
变异方法(Mutation Methods)
Vue.js 包装了被观察数组的变异方法,故它们能触发视图更新。被包装的方法有:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
你可以打开浏览器的控制台,用这些方法修改上例的 items 数组。例如:example1.items.push({ message: 'Baz' })
。
替换数组(Replacing an Array)
变异方法,如名字所示,修改了原始数组。相比之下,也有非变异方法,如 filter(), concat() 和 slice(),不会修改原始数组而是返回一个新数组。在使用非变异方法时,可以直接用新数组替换旧数组:
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})
可能你觉得这将导致 Vue.js 弃用已有 DOM 并重新渲染整个列表——幸运的是并非如此。 Vue.js 实现了一些启发算法,以最大化复用 DOM 元素,因而用另一个数组替换数组是一个非常高效的操作。
Caveats(警告)
因为 JavaScript 的限制,Vue.js 不能检测到下面数组变化(不会触发视图更新):
- 直接用索引设置元素,e.g.
vm.items[indexOfItem] = newValue
- 修改数据的长度, e.g.
vm.items.length = newLength
为了解决问题 (1),Vue.js 扩展了观察数组,为它添加了一个 Vue.set 方法(原来Vue 1.x中是vm.$set):
// Vue.set
Vue.set(example1.items, indexOfItem, newValue)
或者使用splice:
// Array.prototype.splice`
example1.items.splice(indexOfItem, 1, newValue)
至于问题 (2),我们还是用到splice(vm.$remove在Vue 2.x中已经移除):
example1.items.splice(newLength)
显示过滤/排序的结果(Displaying Filtered/Sorted Results)
有时我们想显示过滤/排序过的数组,同时不实际修改或重置原始数据。这种情况下,我们可以创建一个计算属性用来返回过滤/排序过的数组。
例如:
<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
})
}
}
作为另一种选择,我们可以在自定义属性不适用的时候(前面提到过计算属性是惰性的)使用一个方法代替(e.g. 内部嵌套的v-for循环):
<li v-for="n in even(numbers)">{{ n }}</li>
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}