内容提要:
v-for
映射数组与对象的用法
key
值的作用与用法数组变化检测
3.1 数组可变方法
3.2 如何替换数组
3.3 数组index/length赋值不能响应变化的处理方法
对象无法响应变化的处理
如何显示过滤/或排序的结果
v-for
与范围、template上使用v-for
v-for
与v-if
的优先级处理
v-for
在自定义component上的使用
使用v-for
映射一个数组给元素
我们能使用v-for
指令基于数组去渲染一个项目列表。v-for
要求一个特定的语法格式item in items
,items
是数组的数据源,item
是数组元素被迭代的一个别名。
<ul id="example-1">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
var examle1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
Result:
- Foo
- Bar
在v-for
语句块内我们完全可以访问父作用域属性,它也支持可选的第二个参数作为当前item的索引。
<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' }
]
}
})
Result:
你也能够使用of
作为界定符代替in
,这样更接近JavaScript的语法for迭代器:
<div v-for="item of items"></div>
在一个对象中使用v-for
你也能够使用v-for
去迭代一个对象的属性。
<ul id="v-for-object">
<li v-for="value in object">
{{ value }}
</li>
</ul>
new Vue({
el: '#v-for-object',
data: {
object: {
firstName: 'John',
lastName: 'Doe',
age: 30
}
}
})
结果:
- John
- Doe
- 30
你也可以提供第二个参数作为键名:
<div v-for="(value, key) in object">
{{ key }}: {{ value }}
</div>
结果:
firstName: John
astName: Doe
age: 30
增加索引index:
<div v-for="(value, key, index) in object">
{{ index }}. {{ key }}: {{ value }}
</div>
- firstName: John
- lastName: Doe
- age: 30
当遍历一个对象的时候,顺序是基于
Object.keys()
的顺序的key枚举,并不能保证所有JavaScript引擎的实现都是一致的。
key
当Vue正在使用v-for
渲染更新元素列表的时候,默认使用就地复用“in-place patch”策略,如果items的数据顺序已经改变,他会修补每一个元素根据被渲染的个别index,而不是移动元素去匹配items的顺序。这种行为有点像Vue 1.x里的track-by=“$index”
。
这个默认模式是高效的,但是仅仅合适于当你的列表渲染输出不依赖于子组件状态或临时DOM状态(eg:form输入值)。
为了给Vue一个暗示以使它能追踪每一个node‘s的身份,从而重用和重排存在的元素,你需要为每一个item提供一个唯一的key
。这个合适的key
值将作为每一个item的id。这个特殊的属性大致相当于track-by
在 1.x,但是它工作起来像一个属性,所以你能够用v-bind
去绑定动态值(这里用速记法):
<div v-for="item in items" :key="item.id">
<!-- content -->
</div>
Vue建议我们尽可能的为v-for
增加一个key
,除非你迭代的DOM特别简单,或你为了性能提升而依赖默认行为。
由于它是Vue识别节点的通用机制,key
也会被用于其他地方而不仅仅是v-for
,稍后我们可以看到。
数组变化检测
变异方法
Vue封装了一些观察数组变化的方法,这些方法也能够触发视图更新。这些方法如下:
-
push()
向数组尾部添加元素 -
pop()
从数组尾部删除元素 -
shift()
删除一个元素从数组头部 -
unshift()
增加一个元素从数组头部
-
splice()
删除指定位置和数量的数组并返回包含删除项目的新数组。如果有的话。(可以增加新的数组,这是可选的) -
sort()
根据函数排序数组 -
reverse()
翻转数组
我们可以使用前面的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,所以它会用另一个包含相同对象的数组非常高效的替换一个数组。
Caveats
由于JavaScript的限制,Vue不能检测以下数组的改变:
- 当你直接用索引设置一个item的时候,eg:
vm.items[indexOfItem] = newValue
- 当你修改一个数组的长度的时候,eg:
vm.items.length = newLength
例如:
var vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})
vm.items[1] = 'x' // is NOT reactive
vm.items.length = 2 // is NOT reactive
为了解决第一类问题,以下这两个实现是和vm.items[indexOfItem] = newValue
相同的,但都可以出发响应系统的状态更新。
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
你也能够用vm.$set
实例方法,它是Vue.set
的一个别名:
vm.$set(vm.items, indexOfItem, newValue)
为了解决第二类问题,你能用splice
:
vm.items.splice(newLength)
对象改变检测说明
也是由于现代版的JavaScript的限制,Vue不能检测属性的增加或删除。例如:
var vm = new Vue({
data:{
a: 1
}
})
// 'vm.a' is now reactive
vm.b = 2
// 'vm.b' is NOT reactive
对于已经创建的实例,Vue不允许动态的添加新的根级响应属性。然而,使用Vue.set(object, key, value)
方法可以添加一个可响应的属性给到一个被嵌套的对象。例如,给:
var vm = new Vue({
data: {
userProfile: {
name: 'Anika'
}
}
})
你能够用以下方法添加一个age
属性给被嵌套的对象userProfile
:
Vue.set(vm.userProfile, 'age', 27)
同样可以使用全局Vet.set
的一个别名vue.$set
:
vm.$set(vm.userProfile, 'age', 27)
有时你可能想添加多个新属性给一个已经存在的对象,例如使用Object.assign()
或 _.extend()
.在这个例子里,你应该创建一个包含两个对象属性的新对象,所以代替这样做:
Object.assign(vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
你应该增加一个新的,动态响应的属性:
vm.userProfile = Object.assign({}, vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
显示过滤/排序结果
有时我们想要在不改变或重置原始数据的情况下显示一个过滤的或排序后的数组。在这个例子中,你可以创建一个计算属性返回一个已经过滤的或排序后的数组。
例如:
<li v-for="n in evenNumbers">{{ n }}</li>
data: {
numbers: [1, 2, 3, 4, 5]
},
computed: {
eventNumbers: function () {
return this.numbers.filter(function (number){
return number % 2 === 0
})
}
}
在计算属性是不可行的情况下,(eg:在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 nubmer % 2 === 0
})
}
}
在v-for中使用Range
v-for
也能取一个整数,在这个例子中他将重复模板很多次。
<div>
<span v-for="n in 4">{{ n }}</span>
</div>
Result:
在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-for
与v-if
当存在于相同的代码中,v-for
的优先级要高于v-if
。这意味着v-if
将运行在每一次的循环中,这在当你想渲染某些items的时候是有用的,像下面:
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo }}
</li>
以上渲染todos仅仅在complete为not的时候。
相反,如果你想有条件的跳过执行循环,你可以把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
与组件
这部分内容假定你了解组件知识Components。
在一个自定义组件上你能直接使用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
注入component的原因是使组件知道如何与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 templates,因为一个ul标签元素仅仅在一个li标签内部有效,这样做的实现效果与相同,但是可以解决潜在的浏览器错误。更多看 DOM Template Parsing Caveats。
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 = ''
}
}
})