本文 AI 產出,尚未審核

Vue3 教學 – 模板語法(Template Syntax)

主題:key 屬性的作用


簡介

在 Vue3 的模板語法中,key 屬性看似只是一個小小的字串,卻是 提升渲染效能、避免 UI 異常 的關鍵。
當我們在 v-for<transition-group>、條件渲染或是動態組件切換時,如果沒有正確設定 key,Vue 可能會錯誤地重用舊的 DOM,導致資料錯位、動畫卡頓,甚至出現難以偵測的 bug。

本篇文章將從 概念、實作、常見陷阱 三個面向,深入說明 key 的用途與最佳寫法,讓你在開發 Vue3 應用時,能夠 寫出既正確又高效的程式碼


核心概念

1. 為什麼需要 key

Vue 在渲染列表時,會使用 虛擬 DOM diff 演算法 來比較前後兩次的 VNode。
若沒有提供唯一的 key,Vue 只能依賴 索引位置index)來判斷哪個元素被新增、刪除或搬移。
在以下情況下,僅靠索引會產生問題:

  • 資料順序變動(例如排序、過濾)
  • 插入/刪除中間項目
  • 使用過渡動畫<transition-group>

提供唯一且恆定的 key,讓 Vue 能直接對應「舊 VNode ↔ 新 VNode」,從而只更新真正變動的 DOM,避免不必要的重繪。

2. key 的特性

特性 說明
唯一性 每個同層級的 VNode 必須有唯一的 key,否則 diff 會失效。
恆定性 key 應該在資料的生命週期內保持不變,切勿使用會改變的值(如陣列索引)。
類型 key 可以是字串或數字,Vue 內部會將其轉為字串比較。
作用範圍 只在同一層級的 VNode 之間比較,跨層級的 key 不會互相衝突。

3. 使用時機

  1. v-for 迭代列表
    <ul>
      <li v-for="item in items" :key="item.id">{{ item.name }}</li>
    </ul>
    
  2. <transition-group>
    <transition-group name="fade" tag="ul">
      <li v-for="msg in messages" :key="msg.id">{{ msg.text }}</li>
    </transition-group>
    
  3. 條件渲染切換相同組件
    <component :is="currentTab" :key="currentTab"></component>
    
  4. 動態插入/刪除子元素(如拖曳排序、即時搜尋結果)

程式碼範例

以下提供 5 個實用範例,示範不同情境下正確與錯誤的 key 使用方式。

範例 1️⃣ 基本 v-for 與唯一 key

<script setup>
import { ref } from 'vue'

const users = ref([
  { id: 101, name: 'Alice' },
  { id: 102, name: 'Bob' },
  { id: 103, name: 'Carol' }
])
</script>

<template>
  <ul>
    <!-- ✅ 正確:使用唯一的 id 作為 key -->
    <li v-for="user in users" :key="user.id">
      {{ user.name }}
    </li>
  </ul>
</template>

說明user.id 永遠不會變,Vue 能精準追蹤每筆資料,即使列表重新排序也不會出現錯位。


範例 2️⃣ 錯誤使用索引作為 key

<template>
  <ul>
    <!-- ❌ 錯誤:使用 index 作為 key -->
    <li v-for="(item, index) in items" :key="index">
      {{ item.title }}
    </li>
  </ul>
</template>

為什麼會出問題?

items 內部的順序改變(例如排序或插入),index 會隨之改變,導致 Vue 認為「舊的 DOM 被刪除,新的 DOM 被新增」,從而 重新建立整個 <li>,失去過渡動畫、失去表單輸入狀態等。


範例 3️⃣ <transition-group> 搭配 key 產生動畫

<script setup>
import { ref } from 'vue'

const todos = ref([
  { id: 1, text: '買牛奶' },
  { id: 2, text: '寫程式' },
  { id: 3, text: '散步' }
])

function addTodo() {
  const id = Date.now()
  todos.value.unshift({ id, text: `新任務 ${id}` })
}
</script>

<template>
  <button @click="addTodo">新增任務</button>

  <transition-group name="list" tag="ul">
    <li v-for="todo in todos" :key="todo.id" class="list-item">
      {{ todo.text }}
    </li>
  </transition-group>
</template>

<style scoped>
.list-item {
  transition: all 0.3s ease;
}
.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateY(-10px);
}
</style>

重點key 必須唯一,才能讓 <transition-group> 正確辨識「哪一筆資料是新增」或「哪一筆是被移除」,從而觸發對應的 CSS 動畫。


範例 4️⃣ 動態組件切換時使用 key

<script setup>
import { ref } from 'vue'
import Home from './Home.vue'
import Settings from './Settings.vue'

const current = ref('home')
const componentMap = {
  home: Home,
  settings: Settings
}
</script>

<template>
  <!-- 使用 current 作為 key,保證切換時重建組件 -->
  <component :is="componentMap[current]" :key="current"></component>

  <nav>
    <button @click="current = 'home'">Home</button>
    <button @click="current = 'settings'">Settings</button>
  </nav>
</template>

說明:若不加 :key="current",Vue 會嘗試 重用 已存在的組件實例,可能會保留舊組件的狀態(例如表單資料),造成意外行為。加上 key 後,切換時會 銷毀舊組件、重新掛載新組件


範例 5️⃣ 大型表格的虛擬滾動(使用 key 提升效能)

<script setup>
import { ref, computed } from 'vue'

const rows = ref(
  Array.from({ length: 5000 }, (_, i) => ({
    id: i + 1,
    name: `商品 ${i + 1}`,
    price: (Math.random() * 1000).toFixed(2)
  }))
)

const visibleRows = computed(() => rows.value.slice(0, 50)) // 假設只渲染前 50 筆
</script>

<template>
  <table>
    <thead>
      <tr><th>ID</th><th>Name</th><th>Price</th></tr>
    </thead>
    <tbody>
      <tr v-for="row in visibleRows" :key="row.id">
        <td>{{ row.id }}</td>
        <td>{{ row.name }}</td>
        <td>${{ row.price }}</td>
      </tr>
    </tbody>
  </table>
</template>

在大量資料渲染時,唯一且恆定的 key 能讓 Vue 快速定位變動的列,減少 diff 時的計算量,提升 UI 的回應速度。


常見陷阱與最佳實踐

陷阱 可能的結果 正確做法
使用索引作為 key 列表重排時 UI 會錯位、動畫失效 使用資料本身的唯一屬性(如 id
key 變動(例如把 key 設為 item.name,而 name 可編輯) 編輯表單時光標跳動、狀態遺失 key 必須是 不可變 的值
忘記在 <transition-group> 中加 key 過渡動畫不會觸發,或出現錯誤的動畫 為每個子元素設定唯一 key
在同層級使用重複 key Vue 會產生警告,且渲染結果不可預期 確保每個同層 VNode 的 key 唯一
在動態組件切換時未加 key 組件狀態被保留,導致資料混亂 <component> 加上 :key="current"(或其它唯一標識)

最佳實踐清單

  1. 永遠使用唯一且恆定的屬性(如資料庫主鍵 id)作為 key
  2. 避免使用陣列索引,除非列表永遠不會變動(極少情況)。
  3. 在使用過渡動畫或動態組件 時,務必加上 key 以保證正確的掛載/卸載流程。
  4. 對於大型列表,結合虛擬滾動或分頁,仍需保持每筆資料的 key 唯一。
  5. 開發時留意 Vue 控制台的警告[Vue warn] Duplicate keys detected 表示 key 重複,需要立即修正。

實際應用場景

場景 為何需要 key 示例
即時搜尋結果(使用 v-for 顯示過濾後的清單) 使用者輸入關鍵字時,結果會頻繁增減,若 key 不唯一會造成 UI 抖動。 參考範例 1,使用 result.idkey
可拖曳排序清單(如 Trello 卡片) 拖曳時 DOM 位置改變,key 能讓 Vue 只搬移實際的 VNode,保留卡片內部狀態。 v-for="card in cards" :key="card.id"
分頁表格(一次只渲染 20 筆) 切換頁面時列表被重新生成,key 確保每一列的狀態不會被錯誤復用。 參考範例 5。
多語系切換(同一個組件根據語系重新渲染) 語系變更時需要重新載入文字,key 可強制重新渲染整個組件。 <component :is="LangComponent" :key="locale"></component>
表單動態增減欄位(使用 v-for 產生可編輯的欄位) 欄位被刪除或插入時,若 key 為索引,使用者已輸入的內容會被錯位。 v-for="field in fields" :key="field.id"

總結

  • key 是 Vue3 虛擬 DOM diff 的定位錨點,正確使用能大幅提升渲染效能與 UI 正確性。
  • 唯一且恆定key 的核心要件,絕大多數情況下應以資料的主鍵(id)作為 key
  • 列表渲染、過渡動畫、動態組件切換 等場景中,忘記或錯誤使用 key 都會導致 UI 異常或效能下降。
  • 依照本文提供的 最佳實踐,檢查每個 v-for<transition-group><component>,確保都有正確的 key,即可寫出 穩定、可維護、效能佳 的 Vue3 應用。

實務建議:在開發階段就養成「每次使用 v-for 必加 :key」的習慣,並在程式碼審查(code review)時檢查 key 的唯一性與恆定性,這樣可以在問題發生前就把隱藏的 bug 消除。祝你在 Vue3 的旅程中玩得開心、寫得順手!