本文 AI 產出,尚未審核

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>
  • index0 開始計數。
  • 若使用 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 會先 遍歷判斷條件,因此若條件過於複雜,建議改為 computedfilter 後再渲染。

<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 會先遍歷再判斷,效能較差,且條件邏輯容易混亂。 先在 computedfilter 中處理條件,再渲染。
使用非原始值作為 :key 如使用物件本身或陣列,鍵值會隨每次渲染變動,失去追蹤意義。 只使用 字串或數字 的唯一屬性。
迭代大型陣列時未分頁 數千筆資料一次渲染會卡頓。 搭配 虛擬滾動(virtual scroll)分頁
v-for 內部直接改變來源陣列 會觸發不必要的雙向更新,可能產生 意外的副作用 使用 方法(如 addItem()removeItem())來統一管理。

最佳實踐

  1. 永遠提供 :key,且鍵值不會隨渲染變動。
  2. 將過濾、排序、分頁 等邏輯搬到 computed,保持模板純粹。
  3. 若列表項目非常多,考慮使用 Vue Virtual ScrollerIntersectionObserver 實作懶載入。
  4. 嵌套迭代 時,盡量抽離為 子元件,避免單一模板過於龐大。
  5. 使用 TypeScript 時,為迭代變數加上型別,提升 IDE 提示與錯誤偵測。

實際應用場景

  1. 電商商品列表
    • 依照類別、價格範圍、關鍵字即時過濾,搭配分頁或無限捲動。
  2. 即時聊天訊息
    • 使用 v-for 渲染訊息陣列,配合 key 讓新增訊息只插入最底部,避免整體重新渲染。
  3. 管理後台的資料表格
    • 透過 v-for 產生表格列,結合 v-model 實作行內編輯。
  4. 樹狀結構的檔案總管
    • 使用遞迴元件與 v-for 逐層渲染子目錄,支援展開/收合。
  5. 動態表單
    • 依據使用者選擇的欄位類型,v-for 動態產生輸入欄位,配合 v-model 收集資料。

總結

v-for 是 Vue 3 中 列表渲染 的核心指令,透過簡潔的語法即可把任何可迭代資料轉換為 DOM。掌握以下要點,就能在開發過程中寫出 高效、可維護 的程式碼:

  • 必加 :key,確保渲染的穩定性。
  • 把條件與資料處理搬到 JavaScript(computed / method),讓模板保持純粹。
  • 適時使用索引、物件迭代、嵌套結構,配合子元件或遞迴元件提升可讀性。
  • 留意效能:大型陣列請分頁、虛擬滾動或懶載入。

只要遵循這些原則,無論是簡單的清單或是複雜的樹狀結構,都能用 v-for 快速、正確地完成渲染。祝你在 Vue 3 的開發旅程中,玩得開心、寫得順手