Vue3手册译稿 - 基础 - 列表渲染
使用v-for
将数组映射成元素列表
可以使用v-for
指令将数组渲染成一个列表。v-for
指令在使用特殊语法item in items
,items指向数组源,item是迭代 items的每项的别名:
<ul id="array-rendering">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
Vue.createApp({
data() {
return {
items: [{ message: 'Foo' }, { message: 'Bar' }]
}
}
}).mount('#array-rendering')
在v-for
块内我们有权限访问父级属性。同时v-for
提供了第二个参数index
作为循环的索引:
<ul id="array-with-index">
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
Vue.createApp({
data() {
return {
parentMessage: 'Parent',
items: [{ message: 'Foo' }, { message: 'Bar' }]
}
}
}).mount('#array-with-index')
你可以使用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 myObject">
{{ value }}
</li>
</ul>
Vue.createApp({
data() {
return {
myObject: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
}
}).mount('#v-for-object')
同时你可以使用第二个 name参数(a.k.a key)
<li v-for="(value, name) in myObject">
{{ name }}: {{ value }}
</li>
还可以使用第三个参数索引:
<li v-for="(value, name, index) in myObject">
{{ index }}. {{ name }}: {{ value }}
</li>
[info] 提示
迭代一个对象时,根据object.keys()
的列表进行排序,不能保证与javascript引擎实现保持一致!
维护状态
v-for
渲染元素的列表被Vue更新时,默认使用“就地更新”策略。如果数据项的顺序更新了,不使用移动DOM元素来更新顺序,Vue会就地更新元素,并保证每个元素映射到被渲染的位置。
这是一种高效的模式,但这只适用于你的渲染列表不依赖于子组件状态和临时DOM状态(例如表单输入的值)。
Vue会给每一行一个唯一键,从而重用和重新排序每一个现有元素,你需要提供一个唯一的key
属性:
<div v-for="item in items" :key="item.id">
<!-- content -->
</div>
推荐不论是否使用,都为v-for
提供一个唯一的key
,除非迭代非常之简单,或者故障依靠默认行为来提升性能:
因为它是Vue标识唯一节点的通用机制,key
不只仅针对v-for
,还有其它的用途,手册后面会介绍。
[info]
key
不要使用非基本类型如对象或数组。应使用字符或数字。
key
使用方法细节,详见Key 接口文档。
这段太难理解了,从字面意思上理解,当数组排序发生变化时,Vue不会傻傻的一个个搬DOM标签的位置,而是就地更新,比如长度为10的数组,删除第8个,只会把第9,10更新成8,9。然后特别指明如表单输入等临时状态不适用,除非为每一行指定一个临时的key。
下面这段代码,如果不为每行指定一个:key="item.index"
,则删除效果是错误的,因为你输入文本框的内容是“临时的”。
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>vue demo1</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<ul>
<li v-for="(item,index) in users" :key="item.index">
{{index}}-{{item.index}}
<input type="text" />
<button @click="del(index)">删除</button>
</li>
</ul>
</div>
<script type="text/javascript">
Vue.createApp({
data() {
return {
users: [
{index:1,value:''},
{index:2,value:''},
{index:3,value:''},
{index:4,value:''},
{index:5,value:''},
]
}
},
methods: {
del(index) {
if(confirm('确实要删除此行吗?')){
this.users.splice(index,1)
}
}
}
}).mount('#app')
</script>
</body>
</html>
数组变化监控
变更数组的方法
Vue含有一个数组变化的监视器,同时它也会监控视图变化。方法如下:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
原生js方法,自己测试吧,不啰嗦了。
数组替换
例如:filter()
,concat
,slice()
,不会理性原数组,会返回一个新的数组。可以这样把原数组给替换掉:
example1.items = example1.items.filter(item => item.message.match(/Foo/))
你可能会认为这会导致 Vue抛弃原有的DOM并重新渲染整个列表 - 很幸运,Vue智能的最大化重用DOM元素,所以用一个数组替换有部分重叠的另一个数组是非常高效的。
显示过滤/分类的结果
有时候我们想显示过滤或分类后的数组,而不改变或重置原数组。可以使用计算属性来返回这样的结果:
<li v-for="n in evenNumbers" :key="n">{{ n }}</li>
data() {
return {
numbers: [ 1, 2, 3, 4, 5 ]
}
},
computed: {
evenNumbers() {
return this.numbers.filter(number => number % 2 === 0)
}
}
在计算属性不行不通的方案下(例如v-for
循环嵌套),你还可以使用方法:
<ul v-for="numbers in sets">
<li v-for="n in even(numbers)" :key="n">{{ n }}</li>
</ul>
data() {
return {
sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
}
},
methods: {
even(numbers) {
return numbers.filter(number => number % 2 === 0)
}
}
v-for
遍历一个范围
v-for
可以遍历一个整数,下面的示例中会重复模板10次:
<div id="range" class="demo">
<span v-for="n in 10" :key="n">{{ n }} </span>
</div>
v-for
使用在一个<template>
标签上
同v-if
一样,你可以在<template
标签上使用v-for
来渲染多个元素。例如(template标签并不会渲染到html):
<ul>
<template v-for="item in items" :key="item.msg">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>
v-for
同v-if
一同使用
[info] 不推荐两个一起使用。参考风格手册获取细节。
如果它们出现在同一节点中,v-if
优先级高于v-for
。这意味着v-if
不能使用v-for
中的变量。
<!-- 这会抛出错误,因为todo变量未定义. -->
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
</li>
可以把v-if
包裹到<template>
标签内部来解决这个问题:
<template v-for="todo in todos" :key="todo.name">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
组件中使用v-for
[info]这个章节认为你已掌握组件相关知识。如果不了解请先跳过。
你可以像普通元素一样直接在组件上使用v-for
<my-component v-for="item in items" :key="item.id"></my-component>
但这种方式不会传递任何参数到组件,因为组件权局限内部范围。为了把迭代数据传到组件内,我们使用props
:
<my-component
v-for="(item, index) in items"
:item="item"
:index="index"
:key="item.id"
></my-component>
把item
自动注入到组件内部耦合性太强,显式的传递参数可以让组件在其他场景下可复用度更高。看一个待办
列表管理的例子(实现待办列表的增加和删除):
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>v-for-template</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<form v-on:submit.prevent="addNewTodo">
<label for="new-todo">新增待办:</label>
<input id="new-todo" placeholder="例如:喂猫" v-model="newToDoText" />
<button>添加</button>
</form>
<ul>
<todo-item
v-for="(item,index) in todos"
:key="item.id"
:title="item.title"
:index="index"
@remove="remove($event)"
></todo-item>
</ul>
</div>
</body>
<script type="text/javascript">
const data = {
data() {
return {
todos: [
{id:1,title:"洗碗"},{id:2,title:"扔垃圾"},{id:3,title:"修剪草坪"}
],
newToDoText: '',
newToDoId: 4
}
},
methods: {
addNewTodo() {
if(this.newToDoText == ''){
alert('空空空!');
}else {
this.todos.push({
id: this.newToDoId++,
title: this.newToDoText
})
this.newToDoText = ''
}
},
remove(index) {
this.todos.splice(index,1)
}
}
}
const app = Vue.createApp(data)
app.component('todo-item',{
template: `
<li>
{{ title }}
<button @click="removeItem(index)">删除</button>
</li>
`,
props: ['title','index'],
emits: ['remove'],
methods: {
removeItem(item_index){
this.$emit('remove',item_index)
}
}
})
app.mount("#app")
</script>
</html>