Vue 初心者筆記 #12 關於 v-for 的使用細節

之前已經有介紹過 v-for 的使用方式,但是 v-for 在使用上還有許多要注意的地方。
本文將介紹幾個使用 v-for 時可能會遇到的一些小問題,與其他觀念的補充。

1. 陣列與物件 v-for 的索引不同

v-for 使用在陣列與物件上,會有哪些差異呢?

  • 使用於陣列上
    → 索引 (key) 為 0, 1, 2
1
2
3
<li v-for="(item, key) in arrayData">
{{ key }} - {{ item.name }} {{ item.age }} 歲
</li>
  • 使用於物件上
    → 索引 (key) 為 物件的屬性
1
2
3
<li v-for="(item, key) in objectData">
{{ key }} - {{ item.name }} {{ item.age }} 歲
</li>

GITHUB

2. 就地更新(快速置換)

先在 input 照順序打上 1 2 3
反轉陣列後,1 2 3 的順序卻沒改變,為什麼?

1
2
3
4
5
<li v-for="(item, key) in arrayData">
{{ key }} - {{ item.name }} {{ item.age }} 歲
<input type="text">
</li>
<button class="btn" @click="reverseArray">反轉陣列</button>
1
2
3
4
5
// methods 裡的 reverseArray
reverseArray: function () {
this.arrayData.reverse();
console.log(this.arrayData);
},

是什麼原因,造成反轉陣列後,後方的 Input 沒有跟著反轉呢?

GITHUB

原因:因為 Vue 替換 DOM(文件物件模型)元素是使用 就地更新(快速置換) 的方式

官方文件說明

以下說明參考 官方文件

官方文件內容(粗體顯示):

當 Vue 使用 v-for 正在更新已渲染過的元素列表時,它默認使用「就地更新」的策略。

如果數據項的順序被改變,
(以這單元為例,是指 arrayData 資料順序被反轉改變)
Vue 將不會移動 DOM 元素來匹配數據項的順序,
(以這單元為例,是指 input 元素的順序不會跟著 arrayData 資料的新順序改變位置)

而是就地更新每個元素,
(重複、繼續使用原本的 input 元素)
並且確保它們在每個索引位置正確渲染。
(也就是確保顯示、渲染原本順序的 input 元素)

為了給 Vue 一個提示,以便它能跟蹤每個節點的身份,
(每個元素在 DOM 裡面就是一個節點,所以就是要給 Vue 一個提示,讓 Vue 能追蹤到 input 元素)
從而重用和重新排序現有元素,
(指重新排序 input 元素的順序,讓它跟著 arrayData 資料的新順序改變位置)

你需要為每項提供一個唯一的 key 屬性。
(指我們要提供一個唯一的 key 屬性給 input 元素,讓 Vue 能依據這個唯一的 key 屬性追蹤 input 元素,進而讓它跟著 arrayData 資料的新順序改變位置)

理想的 key 值

  • :key 是避免 就地複用(快速替換),直接做強制替換
  • 理想的 key 值是每項都有的唯一 ID
  • 重複值是不能用來作為 key 的

在這個例子中,使用 item.ageitem.name 作為 key 都是可以的,但是不能用 key(:key = “key”)

為什麼?不是分別為 0, 1, 2 嗎?

因為 key 在反轉前與反轉後,順序皆為 0, 1, 2,因此就會變成就地更新的情況

也就是說,反轉前後的陣列,所被綁定的 key 都是 0, 1, 2,並不會因此變成 2, 1, 0

所以才會出現就地更新!

最後,官方建議使用 v-for 都加上 :key

3. 過濾(Filter)

使用 v-for 的時候,常常會使用到過濾

針對上方輸入的文字(filterText),過濾出下方的內容(filterArray)

1
2
3
4
5
6
7
8
<input type="text"
v-model="filterText"
@keyup.enter="filterData">
<ul>
<li v-for="(item, key) in filterArray" :key="item.age">
{{ key }} - {{ item.name }} {{ item.age }} 歲 <input type="text">
</li>
</ul>

這裡的 filterArray 是一個空陣列

1
2
3
4
5
6
7
8
// methods 裡的 filterData
filterData: function () {
var vm = this; // 這個 this 指向 Vue 應用程式
vm.filterArray = vm.arrayData.filter(function (item) {
console.log(vm.filterText, item.name, item.name.match(vm.filterText));
return item.name.match(vm.filterText);
});
},

match()

match() 主要用途是核對字串是否相符。
(這裡是拿 item.namevm.filterText 比較是否相符)

Array.prototype.filter()

filter()forEach() 很像,都是用來處理陣列資料,會將陣列內的每個元素一個個傳入並執行給定的函式一次。
然而 filter() 多了一個 return。
return 後面的內容是 true 的話,便會回傳「陣列內的 單個元素」到指定的變數內。

此段說明參考 JavaScript 陣列處理方法
filter() 會回傳一個陣列,其條件是 return 後方為 true 的物件,很適合用在搜尋符合條件的資料。

在這個例子中:
這裡的 item 代表 arrayData 內的每一個元素(e.g.{name: '小明', age: 16}

使用 match() 比較 item.namevm.filterText 是否相符,
相符便回傳 arrayData 內的單個元素(e.g.{name: '小明', age: 16})到變數 filterData 內。

4. 不能運行的狀況

設長度為零

在原本的 JavaScript 裡,如果把陣列長度改成 0 的話,可以清空陣列。

1
2
3
4
5
// methods 裡的 cantWork
cantWork: function () {
this.arrayData.length = 0;
console.log(this.arrayData);
}

但是在 Vue 裡面,這樣的操作是沒有用的。
雖然 arrayData 的長度確實會被改成零,但資料還是存在。

直接操作陣列

直接指定陣列裡的第 0 個物件並修改資料,雖然在 console 與 Vue 開發者工具中會看到更改,但是畫面資料卻沒有跟著變動。

1
2
3
4
5
6
7
8
// methods 裡的 cantWork
cantWork: function () {
this.arrayData[0] = {
name: '小強',
age: 99,
}
console.log(this.arrayData);
}

在 Vue 中,如果要操作陣列裡的內容,不能透過索引去操作它。
我們必須使用 Vue.set()

解法:Vue.set(target, key, value)

想要操作或新增「原本沒有在 data 裡的資料」時,需要使用 Vue.set(針對目標, 索引, 值) 強制將資料寫入 data。

1
2
3
4
5
6
7
8
// methods 裡的 cantWork
cantWork: function () {
Vue.set(this.arrayData, 0, {
name: '阿強',
age: 99
});
console.log(this.arrayData);
}

5. 純數字的迴圈

除了使用陣列或物件,也可以使用純數字的迴圈。

1
2
3
4
5
<ul>
<li v-for="item in 10">
{{ item }}
</li>
</ul>

使用 v-for="item in 一個數值",數值是多少,迴圈就會跑幾次。

GITHUB

6. Template 的運用

將兩個 tr 一組使用 v-for

1
2
3
4
5
6
7
8
9
10
<table class="table">
<template v-for="item in arrayData">
<tr>
<td>{{ item.age }}</td>
</tr>
<tr>
<td>{{ item.name }}</td>
</tr>
</template>
</table>

7. v-for 與 v-if 混合使用

使用 v-ifv-for 加入年齡限制,改變畫面上呈現的結果。

1
2
3
4
5
<ul>
<li v-for="(item, key) in arrayData" v-if="item.age<=20">
{{ key }} - {{ item.name }} {{ item.age }} 歲
</li>
</ul>

GITHUB

8. v-for 與元件

這邊簡單介紹元件與 v-for 的運用,之後會再詳細介紹元件。

因為現在建議元件使用 v-for 都加上 key,
所以我們想要在元件 list-item 上加入剛才講到的 key 的話,
我們要先定義好元件,接著再加上唯一的值 (ID) 當作 key。

這邊使用的 item.age 就是唯一的值。

1
<list-item :item="item" v-for="(item, key) in arrayData" :key="item.age"></list-item>
1
2
3
4
5
6
7
8
Vue.component('list-item', {
template: `
<li>
{{ item.name }} {{ item.age }} 歲
</li>
`,
props: ['item'],
});

以上資源是我自己整理過後的筆記,若有錯誤歡迎隨時和我聯繫。