8 VUE基礎:列表渲染
我們可以用 v-for
指令基於一個數組來渲染一個列表。v-for
指令需要使用 item in items
形式的特殊語法,其中 items
是源數據數組,而 item
則是被迭代的數組元素的別名。
v-for
代碼塊中可以訪問父作用域的屬性,參考下面代碼中的bookName
。
v-for
還支持一個可選的第二個參數(index
),即當前項的索引,通過將索引加1的結果作爲書籍的序號。
v-for
操作對象:可以用 v-for
來遍歷一個對象的屬性,可以提供第二個的參數爲property
名稱 (也就是鍵名)。還可以用第三個參數index
作爲索引。參考如下例子中的:bookObject
。
<!DOCTYPE html>
<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
屬性:
<div v-for="item in items" v-bind:key="item.id">
<!-- 內容 -->
</div>
建議儘可能在使用 v-for
時提供 key
attribute
,除非遍歷輸出的 DOM 內容非常簡單,或者是刻意依賴默認行爲以獲取性能上的提升。
8.1 數組更新監測
變異方法 (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 元素得到最大範圍的重用而實現了一些智能的啓發式方法,所以用一個含有相同元素的數組去替換原來的數組是非常高效的操作。
應用示例代碼:
<!DOCTYPE html>
<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'
})
完整代碼:
<!DOCTYPE html>
<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>
8.2 顯示過濾/排序結果
我們想要顯示一個數組經過過濾或排序後的版本,而不實際改變或重置原始數據。在這種情況下,可以創建一個計算屬性,來返回過濾或排序後的數組。對於v-for
列表渲染指令,項目中經常使用,但是我們一般從後端接口拿到數據的時候就把數據通過循環整理改造成自己想要的樣子。有時候可能對於不同的列表需求,還要在data裏多造一份數據,這種做法非常累。最好的方式是在v-for
循環的時候對數據進行操作,從而可以做到維護源數據不變。
計算屬性過濾
<!DOCTYPE html>
<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>