在前端開發中,難免動態決定該渲染什麼資料的情況,
舉例來說,今天有一個畫面是 :
要根據 API 回傳的資料來渲染該公司的員工列表
那公司員工這個資料可能不是固定的,因此不太會透過靜態寫死的方式去傳遞,
這個時候就很好的可以使用 v-for
來完成動態渲染的功能了。
<div v-for="(item, index) in items" :key="index"></div>
▲ v-for 基本語法範例1
複數 items
代表你要動態渲染的來源,
單數 item
代表你每次迭代所產生的項目。
<script setup>
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
</script>
<template>
<ul>
<li v-for="item in items" :key="item.name">{{ item.name }}</li>
</ul>
</template>
▲ v-for 基本語法範例2
以上面為例,v-for
傳入一個值,
這個值代表每次迭代會從 items 中取出一個 item,
而後面的 {{}}
就可以放入我們想動態渲染的值,
例如 item
裡面的 name
。
這邊有兩點要注意。
key 屬性是為了讓 Vue 能正確追蹤每個節點,
建議還是要加上去,而且還要具有唯一性。
舉例來說,上面的 :key="item.name
,就是一個反面教材,
因爲名字的重複性實在太高了,
<li v-for="(item, index) in items" :key="index">{{ item.name }}</li>
▲ key 屬性使用
因此可以考慮加上 index
作為 key 值。
注意:當你這個動態渲染的值是有可能修改 (新增,刪除,etc.),那就不建議使用
index
,
因為 v-for 的更新渲染是基於 "in-place patch" 策略,也就是說,
如果真的沒有一個適合的唯一值,又有可能會對內容作修改,
這邊有一個解決方案:利用模板語法進行拼接
如果連這個都有可能重複,那你可能要考慮資料格式是否有需要調整的地方,例如新增確切的唯一值
<li v-for="(item, index) in items" :key="`${item.message}-${index}`">
{{ item.message }}
</li>
▲ 模板語法拼接範例
v-for
的期望值是能接受蠻多種類型的:
number
如果是數字其實蠻有趣的,他會把這個值視為 一個範圍 ,像下面這個就是 1~3。<span v-for="n in 3">{{ n }}</span>
<!-- 要注意 n 的初始值是從 1 開始算 -->
string
這個就更有趣了,如果是字串,就會把你的字拆開來當作 array 迭代。<li v-for="char in 'ithome'">{{ char }}</li>
Array
const nums = ref([1, 2, 3, 4, 5]);
<li v-for="num in nums">{{ num }}</li>
Object
const myObject = ref({
title: "在 Vue 過氣前要學的三十件事",
author: "Yanya",
publishedAt: "2025/09/01",
});
<li v-for="value in myObject">{{ value }}</li>
傳入物件有趣的地方在於你一開始所迭代的值預設是 value
,
但你其實可以取出三個參數,按順序分別是 value
,key
,index
。
<li v-for="(value, key, index) in myObject">
{{ index }}. {{ key }} {{ value }}
</li>
▲ v-for Object 三個參數
Iterable
(Set, Map, Generator…)<script setup>
const mySet = new Set([1, 2, 3]);
const myMap = new Map([
["a", 1],
["b", 2],
]);
// const myGo = "一輩子";
</script>
<template>
<li v-for="val in mySet">{{ val }}</li>
<li v-for="[key, val] in myMap">{{ key }}: {{ val }}</li>
</template>
不知道各位有沒有寫過 for loop
for (let i = 0; i < 5; i++) {
console.log(i)
}
▲ for loop 範例
其實如果我們打開 v-for
的原始碼來看就可以發現,
他本質上都是把我們傳入的 value
透過 new Array()
/ Array.from()
/ Object.keys()
,
轉換成Array,然後對這個 Array 做 for loop,
每次 loop 就做 renderItem()
的動作。
// node_modules>@vue>runtime-core>dist>runtime-core.cjs.js
function renderList(source, renderItem, cache, index) {
// 略
if (sourceIsArray || shared.isString(source)) {
// 略
ret = new Array(source.length);
for (let i = 0, l = source.length; i < l; i++) {
ret[i] = renderItem(
// 略
);
}
} else if (typeof source === "number") {
// 略
ret = new Array(source);
for (let i = 0; i < source; i++) {
ret[i] = renderItem(i + 1, i, void 0, cached && cached[i]);
}
} else if (shared.isObject(source)) {
if (source[Symbol.iterator]) {
ret = Array.from(
source,
(item, i) => renderItem(item, i, void 0, cached && cached[i])
);
} else {
const keys = Object.keys(source);
ret = new Array(keys.length);
for (let i = 0, l = keys.length; i < l; i++) {
// 略
}
▲ v-for 原始碼
這是一個 3.2 版本所推出的一個指令,主要透過緩存機制來優化大量表單渲染。
const list = ref([/* 1000 個以上的物件 */])
<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
<p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p>
<p>...more child nodes</p>
</div>
▲ v-memo 範例程式碼
但這邊有一個事情要注意,由於 v-memo
的機制是,
當傳入值不變,那組件的更新將會被跳過,是直接連 Virtual DOM 都不會產生。
所以正確的指定緩存是必要的,
下面有個例子,可以看到我點擊不同卡片會有選取外框。
但當我不小心把 v-memo
傳入空依賴
<div
v-for="item in items"
:key="item.id"
v-memo="[]"
@click="setActive(item.id)"
>
▲ v-memo 錯誤示範
他直接不更新我的畫面,
但實際上我確實有選到卡片,而眼尖的朋友也發現了,這其實效果就等同於 v-once。
今天我們帶到了非常常用的動態列表渲染指令 v-for
,我們可以傳入什麼樣子的資料去做渲染,
帶到了 v-for
的原始碼讓各位可以更瞭解其背後的原理。
在大量列表渲染中可以使用 v-memo
來優化效能,
以及要注意可能會導致畫面停止更新的細節,其效果跟 v-once
是一樣的。
明天我們將進入基礎語法的最後一篇文,事件綁定 v-on
。
如果你喜歡這個系列或是想看我發瘋,歡迎按下 訂閱
一起走完這三十天吧。
發現我垃圾話真的很多
v-for
的好時機?v-for
的 key
代表什麼,為什麼建議要加上這個屬性。v-for
可以接受哪幾種值,幫我舉三個例子。v-memo
來改善效能,要注意什麼情況下可能導致畫面停止更新。