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. 使用時機
v-for迭代列表<ul> <li v-for="item in items" :key="item.id">{{ item.name }}</li> </ul><transition-group><transition-group name="fade" tag="ul"> <li v-for="msg in messages" :key="msg.id">{{ msg.text }}</li> </transition-group>- 條件渲染切換相同組件
<component :is="currentTab" :key="currentTab"></component> - 動態插入/刪除子元素(如拖曳排序、即時搜尋結果)
程式碼範例
以下提供 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"(或其它唯一標識) |
最佳實踐清單
- 永遠使用唯一且恆定的屬性(如資料庫主鍵
id)作為key。 - 避免使用陣列索引,除非列表永遠不會變動(極少情況)。
- 在使用過渡動畫或動態組件 時,務必加上
key以保證正確的掛載/卸載流程。 - 對於大型列表,結合虛擬滾動或分頁,仍需保持每筆資料的
key唯一。 - 開發時留意 Vue 控制台的警告,
[Vue warn] Duplicate keys detected表示key重複,需要立即修正。
實際應用場景
| 場景 | 為何需要 key |
示例 |
|---|---|---|
即時搜尋結果(使用 v-for 顯示過濾後的清單) |
使用者輸入關鍵字時,結果會頻繁增減,若 key 不唯一會造成 UI 抖動。 |
參考範例 1,使用 result.id 作 key。 |
| 可拖曳排序清單(如 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 的旅程中玩得開心、寫得順手!