Vue.js: 列表渲染 v-for
27 Apr 2017v-for
使用 v-for 迭代陣列或物件中的元素。
例 1:陣列
使用 v-for 迭代陣列中的元素。如下所示,list 是一個陣列,item 代表用於迭代的元素,使用 item.id 或 item.name 可帶出屬性。其中第二個參數 index 是索引值 (optional)。
<div id="app">
<ul>
<li v-for="(item, index) in list">
index: ${ index }, name: ${ item.name }
</li>
</ul>
</div>
var vm = new Vue({
el: '#app',
delimiters: ['${', '}'],
data: {
list: [
{ id: '123456789', name: '選項 1' },
{ id: '234567890', name: '選項 2' },
{ id: '345678901', name: '選項 3' },
],
},
});
// 由於部落格會把使用雙花括號的內容吃掉,所以設定 delimiters 以顯示完整程式碼。
渲染結果如下。


例 2:物件
使用 v-for 迭代物件中的元素。第二個參數 key 是鍵值,第三個參數 index 是索引值,皆為 optional。
<div id="app">
<ul>
<li v-for="(item, key, index) in list">
index: ${ index }, key: ${ key }, name: ${ item.name }
</li>
</ul>
</div>
var vm = new Vue({
el: '#app',
delimiters: ['${', '}'],
data: {
list: {
'123456789': { name: '選項 1' },
'234567890': { name: '選項 2' },
'345678901': { name: '選項 3' },
},
},
});
渲染結果如下。


例 3:使用常數迭代
<div>
<span v-for="n in 10">${ n }</span>
</div>
渲染結果如下。

例 4:<template>
使用 <template> 標籤。
<div id="app">
<ul>
<template v-for="(item, index) in list">
<li>index: ${ index }, name: ${ item.name }</li>
</template>
</ul>
</div>
例 5:元件 Component
可參考 Todo List 的 HTML 部份 和 JavaScript 部份。
key
由於效能考量,在預設的狀況下,Vue.js 會儘量重覆使用已渲染好的元素。
若不設定 key 值,不會重新渲染元素,只會部份更新。
<div id="app">
<ul>
<li v-for="(item, index) in list">${ index } <input type="text" :placeholder="item.name" /></li>
</ul>
</div>
var vm = new Vue({
el: '#app',
delimiters: ['${', '}'],
data: {
list: [
{ id: '123456789', name: '選項 1' },
{ id: '234567890', name: '選項 2' },
{ id: '345678901', name: '選項 3' },
],
},
});
初始畫面,使用者分別在每個輸入框中輸入文字。


使用vm.list.reverse()改變元素順序後,雖然元素被更新,但使用者的輸入被保留,這是因為元素並沒有被重新渲染,而只是部份更新而已。


修改上例,每個 <li> 都使用 v-bind 綁定一個屬性 :key 並設定唯一值,目的是確保每個元素的唯一性,當元素更新,例如改變順序時,有可識別唯一性的 key 來確保重新渲染。
<div id="app">
<ul>
<li v-for="(item, index) in list" :key="item.id">
${ index } <input type="text" :placeholder="item.name" />
</li>
</ul>
</div>
var vm = new Vue({
el: '#app',
delimiters: ['${', '}'],
data: {
list: [
{ id: '123456789', name: '選項 1' },
{ id: '234567890', name: '選項 2' },
{ id: '345678901', name: '選項 3' },
],
},
});
設定 key 值便會重新更新,如下,由於第一個和第三個元素順序改變,因此被重新渲染。

陣列操作
如上所示,使用 vm.list.reverse() 改變元素順序-反序排列,其他操作陣列的 method 還有:
push():新增元素。pop():刪除最新加入的元素。shift():刪除第一個 (即最舊的) 元素。unshift():加入元素至第一個位置。splice():加入或移除元素。sort():由小至大排序。reverse():元素反序排列。filter():過濾陣列的元素,並將符合條件的元素傳回成為一個新陣列。concat():連接陣列,會返回一個新的陣列。slice():切割陣列,會返回一個新的陣列。
顯示過濾 / 排序結果
v-for 迭代的資料為使用 computed 或 methods 處理後的結果。例如:顯示數量大於 6 的水果。
<div id="app">
<ul>
<li v-for="item in filteredFrouts">${ item.name }</li>
</ul>
</div>
var vm = new Vue({
el: '#app',
delimiters: ['${', '}'],
data: {
frouts: [
{
name: 'Apple',
count: 10,
},
{
name: 'Orange',
count: 5,
},
{
name: 'Banana',
count: 20,
},
],
},
computed: {
filteredFrouts: function() {
return this.frouts.filter(function(item) {
return item.count > 6;
});
},
},
});

v-for 與 v-if 優先權的比較
v-for 的優先權高於 v-if,因此當兩者皆出現在同一個元素上時,v-if 會隨著 v-for 重覆執行數次。如下所示,v-if 會執行 10 次,每次都會判斷 n 除以 2 的餘數是否為 1,若為 1 則顯示,否則就隱藏。
<ul>
<li v-if="n % 2 === 1" v-for="n in 10">${ n }</li>
</ul>


注意,v-for 的個數範圍判斷條件成立後,才會輪到 v-if 來判斷顯示與否。這是什麼意思呢?來看下一個例子就知道了。
如下所示,這裡同時出現了 v-for、v-if 和 v-else 三個指令。在這個例子中,我們試圖印出 list 陣列的元素內容,而目前 list 是空陣列,沒有任何項目可顯示。因此,當 v-for 在判斷 item in list 時,發現條件不成立,就不去做 v-if 和 v-else 判斷了,導致原本沒有元素印出時應該要提示的「Nothing could show.」都沒有出現 :(
<div id="app">
<div v-if="list.length !== 0" v-for="item in list">
<div>${ item.name }</div>
</div>
<div v-else>
Nothing could show.
</div>
</div>
new Vue({
el: '#app',
delimiters: ['${', '}'],
data: {
list: [],
},
});
解法如下,讓 v-for 來判斷 list 是否有內容的狀況,v-if 只要負責處理項目為 0 個的情況即可。
<div id="app">
<div v-for="item in list">
<div>${ item.name }</div>
</div>
<div v-if="list.length === 0">
Nothing could show.
</div>
</div>
總結,由於 v-for 優先權較高,當 v-for 執行完條件判斷後,若條件不成立,則後面的 v-if 也不會執行,接著忽略了其後的 v-else。
以上參考自 List Rendering。