v-for
的 key 必須是唯一值,才可以讓 Vue 在更新 v-for
所產生的列表時,能準確更新節點。相反,如果使用 index 作為 key,或者不綁定 key,Vue 就會以該節點的位置作為 key,有機會因為錯誤套用之前渲染過的節點而造成錯誤。
另外,Vue 官方不建議同時使用 v-for
和 v-if
,因為兩者在執行上的優次不同,而且有機會浪費渲染效能。
以下會再作詳情解說。
關於 Vue 更新 v-for
的所產生的畫面,有幾個重點:
v-for
渲染的元素,並非移動 DOM 來完成。因為「重用節點」、「只更新需要更新的節點」這兩個優勢,所以很多人才說:綁 key 可以提升 v-for 的渲染效能。Vue 官方文件提到,如果沒綁 key,就會用最小移動並且盡量原地修改的手法來更新資料。
但接下來的例子,會發現其實不論你是沒有綁 key,還是只綁 index 當作 key。一樣會出現同樣問題。這次我以綁定 index 作為示範。例子是參考了這裏的討論再作調整。
先分享完整程式碼:
https://codesandbox.io/s/todo-list-bang-index-zuo-wei-key-sfs9u?file=/src/App.vue
情況是:
對我們來說,待辦列表由這樣:
變成了:
但 Vue 是使用遍歷來檢查每個節點。因此會做以下的事:
最可怕的是,雖然第二個節點的資料(Watch Netflix)被勾選,但它的 completed 其實是 false:
再沿用以上情況,最後我取消勾選在完成區域裏的 "Buy dinner",會變成以下結果:
"Buy dinner" 仍然是勾選狀態。原理同上,因為對 Vue 來說,第二個節點就是有勾選狀態,你不過是換掉了文字內容,但第二個節點是存在的,並沒有被移除,所以會重用第二個節點的畫面。
要避免以上情況,就不能利用 index 來記錄一個節點的畫面狀態,而是使用唯一值。當我們使用唯一值,Vue 官方文件說明會做以下的事:
key 特殊 attribute 主要用做 Vue 的虚拟 DOM 算法的提示,以在比对新旧节点组时辨识 VNodes。...使用 key 时,它会基于 key 的顺序变化重新排列元素,并且那些使用了已经不存在的 key 的元素将会被移除/销毁。
關於虛擬 DOM 的意思,可參考此系列的文章:
什麼是 Virtual DOM?Vue 如何利用 Virtual DOM?
換言之,如果現在我以 id 當作唯一值,並成為每個節點的 key。從Vue 的文檔中,我們知道當 Vue 遍歷節點時,會做兩件事:
回到例子,這次問題就被解決了。
修改後的程式碼:
https://codesandbox.io/s/todo-list-bang-id-zuo-wei-key-pfy1n?file=/src/App.vue
主要修改部分:
<li v-for="todo in incompletedList" :key="todo.id">
<input type="checkbox" v-model="todo.completed" :id="todo.id" />
<label :for="todo.id">{{ todo.title }}</label>
</li>
當我勾選了 "Buy dinner":
Vue 就會做以下的事:
id: 1
的新舊節點,判斷是否有變化,有的話就重新渲染作更新。即使 "Buy dinner" 被勾選了,勾選的畫面只會套用在 id: 2
的節點上。因此 "Watch Netflix" 不會呈現勾選狀態,因為它的 id 是 3。id: 2
的節點銷毀,並建立 id: 3
的節點。Vue 官方文件有提到,不建議同時使用v-if
和 v-for
。
注意:
v-for
優先於 v-if
v-if
優先於 v-for
因為其中一個語法會優先被執行,加上效能的問題。所以 Vue 官方不建議同時使用。
以 Vue 2 為例,意思是先跑 v-for
呈現每筆資料,之後每筆資料都會套用 v-if
。如果有 1000 筆資料,只有 1 筆是因為 v-if
而不會被渲染,那麼就浪費了 999 個 v-if
的計算。
像以下官方例子:
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo }}
</li>
每個 todo
都會被綁上 v-if
,並計算是否要作顯示。
Vue 官方 style guide 建議兩種常用做法:
computed
把資料處理好,再跑 v-for
渲染已處理好的資料。v-if
移到外層,內層用 v-for
。第一種做法,之前的例子就有用到:
<ul>
<li v-for="todo in completedList" :key="todo.id">
<input type="checkbox" v-model="todo.completed" />
{{ todo.title }}
</li>
</ul>
data() {
return {
todos: [
{
id: "1",
title: "Write blog",
completed: false,
},
...
],
};
},
computed: {
completedList() {
return this.todos.filter((item) => item.completed);
},
},
第二種做法在以上的例子就不能用,因為每個事項都有自己的完成狀態,沒法統一。
v-for
渲染的畫面,Vue 使用「原地更新」的方法來作更新,盡量重用已經渲染過的節點。v-for
需要綁定 key, 因為 Vue 會判斷該節點內容是否有變,以及會紀錄所有 key 的順序並作出更新。v-if
和 v-for
,因為兩者執行時有優次之分,而且有機會浪費渲染效能。vue中v-if和v-for不建议同时使用的坑
Vue2.0 v-for 中 :key 到底有什么用?
重新認識 Vue - 1-6 條件判斷與列表渲染