列表渲染
v-for
我們可以使用 v-for
指令基於一個數組來渲染一個列表。v-for
指令的值需要使用 item in items
形式的特殊語法,其中 items
是源數據的數組,而 item
是迭代項的別名:
js
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
template
<li v-for="item in items">
{{ item.message }}
</li>
在 v-for
塊中可以完整地訪問父作用域內的屬性和變量。v-for
也支持使用可選的第二個參數表示當前項的位置索引。
js
const parentMessage = ref('Parent')
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
template
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
v-for
變量的作用域和下面的 JavaScript 代碼很類似:
js
const parentMessage = 'Parent'
const items = [
/* ... */
]
items.forEach((item, index) => {
// 可以訪問外層的 `parentMessage`
// 而 `item` 和 `index` 只在這個作用域可用
console.log(parentMessage, item.message, index)
})
注意 v-for
是如何對應 forEach
回調的函數簽名的。實際上,你也可以在定義 v-for
的變量別名時使用解構,和解構函數參數類似:
template
<li v-for="{ message } in items">
{{ message }}
</li>
<!-- 有 index 索引時 -->
<li v-for="({ message }, index) in items">
{{ message }} {{ index }}
</li>
對於多層嵌套的 v-for
,作用域的工作方式和函數的作用域很類似。每個 v-for
作用域都可以訪問到父級作用域:
template
<li v-for="item in items">
<span v-for="childItem in item.children">
{{ item.message }} {{ childItem }}
</span>
</li>
你也可以使用 of
作為分隔符來替代 in
,這更接近 JavaScript 的迭代器語法:
template
<div v-for="item of items"></div>
v-for
與對象
你也可以使用 v-for
來遍歷一個對象的所有屬性。遍歷的順序會基於對該對象調用 Object.keys()
的返回值來決定。
js
const myObject = reactive({
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
})
template
<ul>
<li v-for="value in myObject">
{{ value }}
</li>
</ul>
可以通過提供第二個參數表示屬性名 (例如 key):
template
<li v-for="(value, key) in myObject">
{{ key }}: {{ value }}
</li>
第三個參數表示位置索引:
template
<li v-for="(value, key, index) in myObject">
{{ index }}. {{ key }}: {{ value }}
</li>
在 v-for
裡使用範圍值
v-for
可以直接接受一個整數值。在這種用例中,會將該模板基於 1...n
的取值範圍重複多次。
template
<span v-for="n in 10">{{ n }}</span>
注意此處 n
的初值是從 1
開始而非 0
。
<template>
上的 v-for
與模板上的 v-if
類似,你也可以在 <template>
標籤上使用 v-for
來渲染一個包含多個元素的塊。例如:
template
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>
v-for
與 v-if
注意
同時使用 v-if
和 v-for
是不推薦的,因為這樣二者的優先級不明顯。請轉閱風格指南查看更多細節。
當它們同時存在於一個節點上時,v-if
比 v-for
的優先級更高。這意味著 v-if
的條件將無法訪問到 v-for
作用域內定義的變量別名:
template
<!--
這會拋出一個錯誤,因為屬性 todo 此時
沒有在該實例上定義
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
</li>
在外先封装一層 <template>
再在其上使用 v-for
可以解決這個問題 (這也更加明顯易讀):
template
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
通過 key 管理狀態
Vue 默認按照“就地更新”的策略來更新通過 v-for
渲染的元素列表。當數據項的順序改變時,Vue 不會隨之移動 DOM 元素的順序,而是就地更新每個元素,確保它們在原本指定的索引位置上渲染。
默認模式是高效的,但只適用於列表渲染輸出的結果不依賴子組件狀態或者臨時 DOM 狀態 (例如表單輸入值) 的情況。
為了給 Vue 一個提示,以便它可以跟蹤每個節點的標識,從而重用和重新排序現有的元素,你需要為每個元素對應的塊提供一個唯一的 key
attribute:
template
<div v-for="item in items" :key="item.id">
<!-- 內容 -->
</div>
當你使用 <template v-for>
時,key
應該被放置在這個 <template>
容器上:
template
<template v-for="todo in todos" :key="todo.name">
<li>{{ todo.name }}</li>
</template>
注意
key
在這裡是一個通過 v-bind
綁定的特殊 attribute。請不要和在 v-for
中使用對象裡所提到的對象屬性名相混淆。
推薦在任何可行的時候為 v-for
提供一個 key
attribute,除非所迭代的 DOM 內容非常簡單 (例如:不包含組件或有狀態的 DOM 元素),或者你想有意採用默認行為來提高性能。
key
綁定的值期望是一個基礎類型的值,例如字符串或 number 類型。不要用對象作為 v-for
的 key。關於 key
attribute 的更多用途細節,請參閱 key
API 文檔。
組件上使用 v-for
這一小節假設你已了解組件的相關知識,或者你也可以先跳過這裡,之後再回來看。
我們可以直接在組件上使用 v-for
,和在一般的元素上使用沒有區別 (別忘記提供一個 key
):
template
<MyComponent v-for="item in items" :key="item.id" />
但是,這不會自動將任何數據傳遞給組件,因為組件有自己獨立的作用域。為了將迭代後的數據傳遞到組件中,我們還需要傳遞 props:
template
<MyComponent
v-for="(item, index) in items"
:item="item"
:index="index"
:key="item.id"
/>
不自動將 item
注入組件的原因是,這會使組件與 v-for
的工作方式緊密耦合。明確其數據的來源可以使組件在其他情況下重用。
這裡是一個簡單的 Todo List 的例子,展示了如何通過 v-for
來渲染一個組件列表,並向每個實例中傳入不同的數據。
數組變化偵測
變更方法
Vue 能夠偵聽響應式數組的變更方法,並在它們被調用時觸發相關的更新。這些變更方法包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
替換一個數組
變更方法,顧名思義,就是會對調用它們的原數組進行變更。相對地,也有一些不可變 (immutable) 方法,例如 filter()
,concat()
和 slice()
,這些都不會更改原數組,而總是返回一個新數組。當遇到的是非變更方法時,我們需要將舊的數組替換為新的:
js
// `items` 是一個數組的 ref
items.value = items.value.filter((item) => item.message.match(/Foo/))
你可能認為這將導致 Vue 丟棄現有的 DOM 並重新渲染整個列表——幸運的是,情況並非如此。Vue 實現了一些巧妙的方法來最大化對 DOM 元素的重用,因此用另一個包含部分重疊對象的數組來做替換,仍會是一種非常高效的操作。
展示過濾或排序後的結果
有時,我們希望顯示數組經過過濾或排序後的內容,而不實際變更或重置原始數據。在這種情況下,你可以創建返回已過濾或已排序數組的計算屬性。
舉例來說:
js
const numbers = ref([1, 2, 3, 4, 5])
const evenNumbers = computed(() => {
return numbers.value.filter((n) => n % 2 === 0)
})
template
<li v-for="n in evenNumbers">{{ n }}</li>
在計算屬性不可行的情況下 (例如在多層嵌套的 v-for
循環中),你可以使用以下方法:
js
const sets = ref([
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10]
])
function even(numbers) {
return numbers.filter((number) => number % 2 === 0)
}
template
<ul v-for="numbers in sets">
<li v-for="n in even(numbers)">{{ n }}</li>
</ul>
在計算屬性中使用 reverse()
和 sort()
的時候務必小心!這兩個方法將變更原始數組,計算函數中不應該這麼做。請在調用這些方法之前創建一個原數組的副本:
diff
- return numbers.reverse()
+ return [...numbers].reverse()