我们可以用 v-for
指令基于一个数组来渲染一个列表。v-for
指令需要使用 item in items
形式的特殊语法,其中 items
是源数据数组,而 item
则是被迭代的数组元素的别名。
v-for
代码块中可以访问父作用域的属性,参考下面代码中的bookName
。
v-for
还支持一个可选的第二个参数(index
),即当前项的索引,通过将索引加1的结果作为书籍的序号。
v-for
操作对象:可以用 v-for
来遍历一个对象的属性,可以提供第二个的参数为property
名称 (也就是键名)。还可以用第三个参数index
作为索引。参考如下例子中的:bookObject
。
<html>
<head>
<meta charset="UTF-8">
<title>VUE 列表渲染title>
head>
<body>
<div id='app'>
<h2>v-for遍历数组h2>
<ul>
<li v-for="(book, index) in books" :key="index">
{{index +1 }}____{{bookName}}{{book.name}}____{{bookPrice}}{{book.price}}
li>
ul>
<hr>
<h2>v-for操作对象h2>
<ul>
<li v-for="(value, name, index) in bookObject">
{{index+1}} : {{name}} : {{ value }}
li>
ul>
div>
body>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
<script type="text/javascript">
const vm = new Vue({
el : '#app',
data : {
bookName: '书名:',
bookPrice: '价格:',
books: [
{name: 'Vue入门', price:28},
{name: 'Vue精通', price:36},
{name: 'Vue实战', price:69},
{name: 'Vue从入门到放弃', price:98}
],
bookObject: {
bookname: "Vue从入门到放弃",
price: 98,
author: '黑白猿',
publishedAt: '2020-10-10'
}
}
})
script>
html>
当Vue正在更新使用 v-for
渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。
为了给Vue
一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,需要为每项提供一个唯一 key
属性:
建议尽可能在使用 v-for
时提供 key
attribute
,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。
变异方法 (mutation method):顾名思义,会改变调用了这些方法的原始数组。Vue 将被侦听的数组的变异方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的变异方法包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
可以通过打开控制台,直接对前面例子的 items
数组尝试调用变异方法。比如 vm.books.push({name:"vue深入解析", price: 38})
。
相比变异方法,也有非变异 (non-mutating method) 方法,例如 filter()
、concat()
和 slice()
。它们不会改变原始数组,而总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组。你可能认为这将导致 Vue 丢弃现有DOM并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。
应用示例代码:
<html>
<head>
<meta charset="UTF-8">
<title>VUE 列表渲染title>
head>
<body>
<div id='app'>
<h2>v-for遍历数组h2>
<ul>
<li v-for="(book, index) in books" :key="index">
{{index +1 }}____{{bookName}}{{book.name}}____{{bookPrice}}{{book.price}} =======<button @click="delBook(index)">删除button>
li>
ul>
<button @click="addBook({name:'vue深入解析', price: 38})">添加图书button>
<hr>
<h2>v-for操作对象h2>
<ul>
<li v-for="(value, name, index) in bookObject">
{{index+1}} : {{name}} : {{ value }}
li>
ul>
div>
body>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
<script type="text/javascript">
const vm = new Vue({
el : '#app',
data : {
bookName: '书名:',
bookPrice: '价格:',
books: [
{name: 'Vue入门', price:28},
{name: 'Vue精通', price:36},
{name: 'Vue实战', price:69},
{name: 'Vue从入门到放弃', price:98}
],
bookObject: {
bookname: "Vue从入门到放弃",
price: 98,
author: '黑白猿',
publishedAt: '2020-10-10'
}
},
methods: {
addBook(newbook){
this.books.push(newbook);
},
delBook(index) {
this.books.splice(index, 1);
}
},
})
script>
html>
对于已经创建的实例,Vue
不允许动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, propertyName, value)
方法向嵌套对象添加响应式属性。例如,对于:
var vm = new Vue({
data: {
userProfile: {
name: 'Anika'
}
}
})
你可以添加一个新的 age
属性到嵌套的 userProfile
对象:
Vue.set(vm.userProfile, 'age', 27)
你还可以使用 vm.$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'
})
完整代码:
<html>
<head>
<meta charset="UTF-8">
<title>VUE 列表渲染title>
head>
<body>
<div id='app'>
<h2>v-for遍历数组h2>
<ul>
<li v-for="(book, index) in books" :key="index">
{{index +1 }}____{{bookName}}{{book.name}}____{{bookPrice}}{{book.price}} =======<button @click="delBook(index)">删除button>
li>
ul>
<button @click="addBook({name:'vue深入解析', price: 38})">添加图书button>
<hr>
<h2>v-for操作对象h2>
<ul>
<li v-for="(value, name, index) in bookObject">
{{index+1}} : {{name}} : {{ value }}
li>
ul>
<hr>
<h2>动态添加响应性属性h2>
<ul>
<li v-for="(value, name, index) in userProfile">
{{index+1}} : {{name}} : {{ value }}
li>
ul>
<button @click="addProperties">添加属性button>
div>
body>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
<script type="text/javascript">
const vm = new Vue({
el : '#app',
data : {
bookName: '书名:',
bookPrice: '价格:',
books: [
{name: 'Vue入门', price:28},
{name: 'Vue精通', price:36},
{name: 'Vue实战', price:69},
{name: 'Vue从入门到放弃', price:98}
],
bookObject: {
bookname: "Vue从入门到放弃",
price: 98,
author: '黑白猿',
publishedAt: '2020-10-10'
},
userProfile: {
name: '黑白猿'
}
},
methods: {
addBook(newbook){
this.books.push(newbook);
},
delBook(index) {
this.books.splice(index, 1);
},
addProperties(){
vm.userProfile = Object.assign({}, vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
console.log(vm.userProfile)
}
},
})
script>
html>
我们想要显示一个数组经过过滤或排序后的版本,而不实际改变或重置原始数据。在这种情况下,可以创建一个计算属性,来返回过滤或排序后的数组。对于v-for
列表渲染指令,项目中经常使用,但是我们一般从后端接口拿到数据的时候就把数据通过循环整理改造成自己想要的样子。有时候可能对于不同的列表需求,还要在data里多造一份数据,这种做法非常累。最好的方式是在v-for
循环的时候对数据进行操作,从而可以做到维护源数据不变。
计算属性过滤
<html>
<head>
<meta charset="UTF-8">
<title>VUE 列表渲染title>
head>
<body>
<div id="demo">
<h2>列表渲染之排序、过滤h2>
<input type="text" v-model="searchName">
<ul>
<li v-for="(p, index) in filterPersons" :key="index">
{{index}}--{{p.name}}--{{p.age}}
li>
ul>
<div>
<button @click="setOrderType(2)">年龄升序button>
<button @click="setOrderType(1)">年龄降序button>
<button @click="setOrderType(0)">原本顺序button>
div>
div>
body>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
<script type="text/javascript">
const vm2 = new Vue({
el: '#demo',
data: {
searchName: '',
orderType: 0, // 0代表不排序, 1代表降序, 2代表升序
persons: [
{name: 'Tom', age:18},
{name: 'Jack', age:17},
{name: 'Bob', age:19},
{name: 'Mary', age:16}
]
},
computed: {
filterPersons () {
// 取出相关数据
const {searchName, persons, orderType} = this
let fPersons = [...persons]
// 过滤数组
if(searchName.trim()) {
fPersons = persons.filter(p => p.name.indexOf(searchName)!==-1)
}
// 排序
if(orderType) {
fPersons.sort(function (p1, p2) {
if(orderType===1) { // 降序
return p2.age-p1.age
} else { // 升序
return p1.age-p2.age
}
})
}
return fPersons
}
},
methods: {
setOrderType (orderType) {
this.orderType = orderType
}
}
})
script>
html>