Vue3 教學 – 模板語法(Template Syntax)
主題:v-for 列表渲染
簡介
在單頁應用(SPA)中,列表渲染是最常見的需求之一。無論是商品目錄、留言版還是動態表格,都需要把陣列或物件的每一筆資料對應到畫面上的 DOM 元素。Vue 3 提供的 v-for 指令,讓開發者可以以宣告式的方式快速完成這項工作,同時保留 Vue 的響應式優勢。
本篇文章將從 核心概念、實作範例、常見陷阱,到 最佳實踐 與 實際應用場景,一步步說明 v-for 的使用方式,適合 初學者 了解基礎,也能讓 中級開發者 掌握更進階的技巧。
核心概念
1. 基本語法
v-for 的基本語法為:
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
item為迭代變數,代表陣列中的每一筆資料。items必須是可迭代的資料結構(Array、Object、Set、Map)。:key為唯一識別鍵,Vue 依據它來追蹤每個節點,提升渲染效能。
⚠️ 小提醒:在 Vue 3 中,
v-for必須搭配:key,否則會出現 不必要的重新渲染。
2. 同時取得索引
有時候需要知道當前項目的索引值,只要在迭代變數後加上逗號即可:
<li v-for="(item, index) in items" :key="item.id">
{{ index + 1 }}. {{ item.name }}
</li>
index從 0 開始計數。- 若使用 1 為起點,可直接在模板中
{{ index + 1 }}。
3. 迭代物件(Object)
v-for 也能遍歷物件的屬性,語法稍有不同:
<div v-for="(value, key, i) in userInfo" :key="key">
{{ i }}. {{ key }}: {{ value }}
</div>
value為屬性值,key為屬性名稱,i為索引(可選)。- 迭代物件時,
key必須是唯一的字串或 Symbol。
4. 嵌套迭代
在多層結構(如樹狀目錄)時,可以將 v-for 直接嵌套:
<ul>
<li v-for="category in categories" :key="category.id">
{{ category.name }}
<ul>
<li v-for="product in category.products" :key="product.id">
{{ product.title }}
</li>
</ul>
</li>
</ul>
技巧:盡量把資料的 層級 在 JavaScript 端整理好,避免在模板中寫過多的邏輯。
5. 使用 v-for 與其他指令共存
v-for 可以與 v-if 同時使用,但要注意執行順序。Vue 會先 遍歷 再 判斷條件,因此若條件過於複雜,建議改為 computed 或 filter 後再渲染。
<li v-for="item in items" :key="item.id" v-if="item.visible">
{{ item.name }}
</li>
程式碼範例
以下示範 5 個常見情境,皆附上註解說明。
範例 1:最簡單的陣列渲染
<template>
<ul>
<!-- items 為字串陣列 -->
<li v-for="fruit in fruits" :key="fruit">{{ fruit }}</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const fruits = ref(['蘋果', '香蕉', '葡萄'])
</script>
ref讓陣列具備響應式,新增或刪除時畫面會自動更新。
範例 2:帶索引的渲染與條件樣式
<template>
<ul>
<li
v-for="(task, idx) in tasks"
:key="task.id"
:class="{ done: task.done }"
>
{{ idx + 1 }}. {{ task.title }}
</li>
</ul>
</template>
<script setup>
import { reactive } from 'vue'
const tasks = reactive([
{ id: 1, title: '寫教學文章', done: true },
{ id: 2, title: '檢查程式碼', done: false },
{ id: 3, title: '發布部落格', done: false },
])
</script>
<style scoped>
.done { text-decoration: line-through; color: #888; }
</style>
重點:使用
reactive讓物件陣列保持響應式,並利用:class動態切換樣式。
範例 3:迭代物件(Object)
<template>
<dl>
<div v-for="(value, key) in userInfo" :key="key">
<dt>{{ key }}</dt>
<dd>{{ value }}</dd>
</div>
</dl>
</template>
<script setup>
import { ref } from 'vue'
const userInfo = ref({
姓名: '陳小明',
年齡: 28,
城市: '台北',
})
</script>
這裡使用
<dl>(定義清單)呈現「鍵-值」對,適合說明性資訊。
範例 4:嵌套迭代(樹狀目錄)
<template>
<ul>
<li v-for="dept in departments" :key="dept.id">
{{ dept.name }}
<ul>
<li v-for="emp in dept.employees" :key="emp.id">
{{ emp.name }} ({{ emp.title }})
</li>
</ul>
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const departments = ref([
{
id: 1,
name: '研發部',
employees: [
{ id: 101, name: '林佳慧', title: '前端工程師' },
{ id: 102, name: '張偉', title: '後端工程師' },
],
},
{
id: 2,
name: '行銷部',
employees: [
{ id: 201, name: '王小美', title: '企劃' },
],
},
])
</script>
建議:如果資料層級更深,考慮抽離為 遞迴元件(recursive component)以提升可維護性。
範例 5:結合 computed 進行過濾與分頁
<template>
<input v-model="search" placeholder="搜尋商品…" />
<ul>
<li
v-for="product in pagedProducts"
:key="product.id"
>{{ product.name }} - ${{ product.price }}</li>
</ul>
<button @click="prevPage" :disabled="page === 1">上一頁</button>
<button @click="nextPage" :disabled="page >= totalPages">下一頁</button>
</template>
<script setup>
import { ref, computed } from 'vue'
const products = ref([
/* 假設有 30 筆商品資料 */
{ id: 1, name: '筆記型電腦', price: 25000 },
/* ... */
{ id: 30, name: '滑鼠', price: 500 },
])
const search = ref('')
const page = ref(1)
const pageSize = 5
const filtered = computed(() => {
return products.value.filter(p =>
p.name.toLowerCase().includes(search.value.toLowerCase())
)
})
const totalPages = computed(() => Math.ceil(filtered.value.length / pageSize))
const pagedProducts = computed(() => {
const start = (page.value - 1) * pageSize
return filtered.value.slice(start, start + pageSize)
})
function prevPage() { if (page.value > 1) page.value-- }
function nextPage() { if (page.value < totalPages.value) page.value++ }
</script>
透過 computed,所有過濾與分頁邏輯都在 JavaScript 層完成,模板只負責渲染,保持乾淨與可讀。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
缺少 :key |
Vue 會使用陣列的索引作為預設鍵,導致列表重排時產生 不必要的 DOM 更新。 | 為每筆資料提供 唯一且穩定 的鍵(如 id)。 |
在 v-for 中直接寫 v-if |
會先遍歷再判斷,效能較差,且條件邏輯容易混亂。 | 先在 computed 或 filter 中處理條件,再渲染。 |
使用非原始值作為 :key |
如使用物件本身或陣列,鍵值會隨每次渲染變動,失去追蹤意義。 | 只使用 字串或數字 的唯一屬性。 |
| 迭代大型陣列時未分頁 | 數千筆資料一次渲染會卡頓。 | 搭配 虛擬滾動(virtual scroll) 或 分頁。 |
在 v-for 內部直接改變來源陣列 |
會觸發不必要的雙向更新,可能產生 意外的副作用。 | 使用 方法(如 addItem()、removeItem())來統一管理。 |
最佳實踐:
- 永遠提供
:key,且鍵值不會隨渲染變動。 - 將過濾、排序、分頁 等邏輯搬到 computed,保持模板純粹。
- 若列表項目非常多,考慮使用 Vue Virtual Scroller 或 IntersectionObserver 實作懶載入。
- 在 嵌套迭代 時,盡量抽離為 子元件,避免單一模板過於龐大。
- 使用 TypeScript 時,為迭代變數加上型別,提升 IDE 提示與錯誤偵測。
實際應用場景
- 電商商品列表
- 依照類別、價格範圍、關鍵字即時過濾,搭配分頁或無限捲動。
- 即時聊天訊息
- 使用
v-for渲染訊息陣列,配合key讓新增訊息只插入最底部,避免整體重新渲染。
- 使用
- 管理後台的資料表格
- 透過
v-for產生表格列,結合v-model實作行內編輯。
- 透過
- 樹狀結構的檔案總管
- 使用遞迴元件與
v-for逐層渲染子目錄,支援展開/收合。
- 使用遞迴元件與
- 動態表單
- 依據使用者選擇的欄位類型,
v-for動態產生輸入欄位,配合v-model收集資料。
- 依據使用者選擇的欄位類型,
總結
v-for 是 Vue 3 中 列表渲染 的核心指令,透過簡潔的語法即可把任何可迭代資料轉換為 DOM。掌握以下要點,就能在開發過程中寫出 高效、可維護 的程式碼:
- 必加
:key,確保渲染的穩定性。 - 把條件與資料處理搬到 JavaScript(computed / method),讓模板保持純粹。
- 適時使用索引、物件迭代、嵌套結構,配合子元件或遞迴元件提升可讀性。
- 留意效能:大型陣列請分頁、虛擬滾動或懶載入。
只要遵循這些原則,無論是簡單的清單或是複雜的樹狀結構,都能用 v-for 快速、正確地完成渲染。祝你在 Vue 3 的開發旅程中,玩得開心、寫得順手!