Vue3 動態 class 綁定
簡介
在 Vue3 中,template 是與資料互動的第一線介面,而「class」的綁定則是最常見的樣式切換需求。透過動態 class 綁定,我們可以根據資料狀態自動切換 CSS 樣式,讓 UI 的呈現更具彈性與可維護性。
對於 初學者 來說,掌握 v‑bind:class(簡寫為 :class)的用法是建立互動式介面的基礎;對 中階開發者 則是實作條件樣式、主題切換、動畫狀態管理等進階功能的關鍵。本文將從概念說明、實作範例、常見陷阱到最佳實踐,完整呈現 Vue3 中動態 class 綁定的全貌,幫助你在專案中快速上手並寫出乾淨、可讀的程式碼。
核心概念
1. :class 的三種語法
Vue3 提供了三種常見的寫法,分別適用於不同情境:
| 語法 | 資料型別 | 說明 |
|---|---|---|
:class="obj" |
Object | 以物件鍵值對表示「class 名稱」與「是否啟用」的布林值。 |
:class="arr" |
Array | 以陣列列出需要套用的 class 名稱,支援條件運算子。 |
:class="computedProp" |
Computed / Function | 透過 computed 屬性或回傳函式產生更彈性的 class 結構。 |
Tip:
v-bind:class可以與:style同時使用,兩者皆支援物件與陣列語法,保持一致的寫法風格有助於程式碼可讀性。
2. 物件語法(Object Syntax)
最直觀的方式是以 key 為 class 名稱,value 為布林值或可求值的表達式。當 value 為 true 時,對應的 class 會被加入;為 false 時則不會。
範例 1:簡單的條件 class
<template>
<button :class="{ active: isActive }" @click="toggle">
點我切換 active
</button>
</template>
<script setup>
import { ref } from 'vue'
const isActive = ref(false)
function toggle() {
isActive.value = !isActive.value
}
</script>
<style scoped>
button {
padding: 0.5rem 1rem;
border: 2px solid #888;
}
.active {
border-color: #42b983;
background-color: #e0f5eb;
}
</style>
說明:
isActive為ref,每次點擊會切換布林值,activeclass 會依值自動加減。
範例 2:多條件物件
<template>
<div :class="{ error: hasError, warning: hasWarning, success: isSuccess }">
訊息框
</div>
</template>
<script setup>
import { ref } from 'vue'
const hasError = ref(false)
const hasWarning = ref(true)
const isSuccess = ref(false)
</script>
<style scoped>
.error { color: #d9534f; }
.warning { color: #f0ad4e; }
.success { color: #5cb85c; }
</style>
重點:一次綁定多個 class,只要對應的布林值為
true,該 class 就會被套用。
3. 陣列語法(Array Syntax)
陣列語法讓我們可以 動態組合 多個 class 名稱,特別適合需要根據條件挑選不同 class 時使用。
範例 3:條件加入 class
<template>
<p :class="[isRed ? 'red' : 'blue', sizeClass]">
文字顏色與大小會變
</p>
<button @click="toggleColor">切換顏色</button>
<button @click="toggleSize">切換大小</button>
</template>
<script setup>
import { ref, computed } from 'vue'
const isRed = ref(true)
const isLarge = ref(false)
function toggleColor() { isRed.value = !isRed.value }
function toggleSize() { isLarge.value = !isLarge.value }
const sizeClass = computed(() => (isLarge.value ? 'large' : 'small'))
</script>
<style scoped>
.red { color: #e74c3c; }
.blue { color: #3498db; }
.large { font-size: 2rem; }
.small { font-size: 1rem; }
</style>
說明:陣列中的每一個元素都可以是字串、計算屬性或三元運算子,最終會被 Vue 合併成單一 class 列表。
範例 4:與 v‑for 結合的動態 class
<template>
<ul>
<li v-for="item in items" :key="item.id"
:class="['item', { selected: item.id === selectedId }]">
{{ item.name }}
</li>
</ul>
<button @click="selectRandom">隨機選取</button>
</template>
<script setup>
import { ref } from 'vue'
const items = ref([
{ id: 1, name: '蘋果' },
{ id: 2, name: '香蕉' },
{ id: 3, name: '橙子' },
])
const selectedId = ref(null)
function selectRandom() {
const random = items.value[Math.floor(Math.random() * items.value.length)]
selectedId.value = random.id
}
</script>
<style scoped>
.item { padding: 0.4rem; cursor: pointer; }
.selected { background-color: #dff0d8; }
</style>
技巧:在
v-for中使用陣列語法,能同時保留固定 class(如'item')與條件 class(selected),寫法更清晰。
4. 計算屬性(Computed)與函式返回的 class
當 class 的組合邏輯變得複雜,直接在 template 寫長長的表達式會降低可讀性。此時可將邏輯抽到 computed 或 method 中,回傳符合 Vue 期望的物件或陣列。
範例 5:使用 computed 產生物件
<template>
<nav :class="navClass">
<a href="#" :class="linkClass('home')">Home</a>
<a href="#" :class="linkClass('about')">About</a>
<a href="#" :class="linkClass('contact')">Contact</a>
</nav>
</template>
<script setup>
import { ref, computed } from 'vue'
const currentPage = ref('home')
function linkClass(page) {
return {
link: true,
active: currentPage.value === page,
}
}
const navClass = computed(() => ({
navbar: true,
dark: true,
}))
</script>
<style scoped>
.navbar { display: flex; gap: 1rem; }
.dark { background: #333; color: #fff; }
.link { padding: 0.5rem; text-decoration: none; color: inherit; }
.active { font-weight: bold; border-bottom: 2px solid #42b983; }
</style>
重點:
linkClass回傳 object,讓每個連結根據當前頁面自動加上active。navClass使用 computed 讓 nav 本身的樣式也能根據狀態變化(例如暗色/亮色主題切換)。
5. 多層級(Nested)class 綁定
在大型專案中,常會需要 巢狀 的 class 結構,例如 BEM 命名法或 CSS Modules。Vue3 的 :class 完全支援巢狀物件,只要層級正確,最終會被展平成一個 class 列表。
範例 6:BEM 風格的巢狀 class
<template>
<div :class="blockClass">
<h2 :class="`${block}__title`">標題</h2>
<p :class="titleMods">說明文字</p>
<button :class="buttonClass" @click="toggle">切換狀態</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const block = 'card'
const isPrimary = ref(true)
const blockClass = computed(() => ({
[block]: true,
[`${block}--primary`]: isPrimary.value,
}))
const titleMods = computed(() => ({
[`${block}__title`]: true,
[`${block}__title--highlight`]: !isPrimary.value,
}))
const buttonClass = computed(() => ({
[`${block}__btn`]: true,
[`${block}__btn--active`]: isPrimary.value,
}))
function toggle() { isPrimary.value = !isPrimary.value }
</script>
<style scoped>
.card { border: 1px solid #ccc; padding: 1rem; }
.card--primary { background: #f0f9eb; }
.card__title { font-size: 1.2rem; }
.card__title--highlight { color: #e67e22; }
.card__btn { margin-top: 0.5rem; }
.card__btn--active { background: #42b983; color: #fff; }
</style>
說明:利用 computed 結合 模板字面量(template literal)產生 BEM class,讓樣式與狀態高度耦合且易於維護。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 / Best Practice |
|---|---|---|
| 1. 直接在 template 寫長表達式 | 會讓 template 雜亂、難以除錯。 | 把邏輯搬到 computed 或 methods,保持 template 簡潔。 |
| 2. 物件鍵名寫成變數時忘記方括號 | :class="{ activeClass: isActive }" 會把字面量 activeClass 當成 class 名稱。 |
使用 計算屬性 或 方括號::{ [activeClass] : isActive }。 |
3. 陣列裡混入 undefined/null |
Vue 會忽略,但若不小心產生大量 undefined,會影響效能。 |
在產生陣列前先過濾:computed(() => classes.filter(Boolean))。 |
4. CSS Module 與 :class 混用忘記 :class="styles.xxx" |
直接寫字串會無法對應到模組化的 class 名稱。 | 使用 import styles from './Component.module.css',然後 :class="styles.active"。 |
5. 動態 class 與 scoped 樣式衝突 |
scoped 會自動加上 data‑attribute,若 class 名稱在外部樣式表中定義,可能找不到。 |
確認樣式來源一致,或改用全局 CSS(<style> 無 scoped)配合 :deep()。 |
最佳實踐
- 盡量使用物件語法:可讀性最高,條件明確。
- 將複雜邏輯抽離:使用
computed產生最終的 class 物件或陣列。 - 保持 class 命名一致:建議採用 BEM 或 CSS Modules,避免全局污染。
- 結合 CSS 變數:動態 class 只負責切換狀態,樣式細節交給 CSS 變數管理,提升可維護性。
- 測試渲染結果:開發時可在 Chrome DevTools 中檢查
<element class="...">,確保 class 正確加入或移除。
實際應用場景
1. 主題切換(Dark / Light)
<template>
<div :class="themeClass">
<button @click="toggleTheme">切換主題</button>
<slot />
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const isDark = ref(false)
function toggleTheme() { isDark.value = !isDark.value }
const themeClass = computed(() => ({
'theme-light': !isDark.value,
'theme-dark' : isDark.value,
}))
</script>
<style scoped>
.theme-light { background: #fff; color: #222; }
.theme-dark { background: #222; color: #eee; }
</style>
說明:全局 wrapper 只依靠一個 class (
theme-light/theme-dark) 來切換整體樣式,配合 CSS 變數即可在子元件中共用色彩設定。
2. 表單驗證錯誤樣式
<template>
<form @submit.prevent="submit">
<input v-model="email"
:class="{ 'input': true, 'is-invalid': emailError }"
placeholder="Email" />
<p v-if="emailError" class="error-msg">請輸入有效的 Email</p>
<button type="submit">送出</button>
</form>
</template>
<script setup>
import { ref } from 'vue'
const email = ref('')
const emailError = ref(false)
function submit() {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
emailError.value = !re.test(email.value)
}
</script>
<style scoped>
.input { padding: 0.5rem; border: 1px solid #ccc; }
.is-invalid { border-color: #e74c3c; background-color: #fdecea; }
.error-msg { color: #e74c3c; margin-top: 0.2rem; }
</style>
重點:只要
emailError為true,is-invalidclass 即被加入,樣式自動切換。
3. 動畫狀態(Enter / Leave)
<template>
<transition name="fade">
<div v-if="show" :class="['box', { rotating: isRotating }]">
旋轉方塊
</div>
</transition>
<button @click="show = !show">Toggle</button>
<button @click="isRotating = !isRotating">Rotate</button>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(true)
const isRotating = ref(false)
</script>
<style scoped>
.fade-enter-active, .fade-leave-active { transition: opacity .3s; }
.fade-enter-from, .fade-leave-to { opacity: 0; }
.box { width: 120px; height: 120px; background:#42b983; margin:1rem; }
.rotating { animation: spin 2s linear infinite; }
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
</style>
說明:
rotatingclass 控制動畫,show控制是否渲染,兩者可獨立切換,展示了動態 class 與 Vue transition 的結合。
總結
- 動態 class 綁定 是 Vue3 中最常用且最具威力的 UI 控制手段之一。
- 透過 物件語法、陣列語法 以及 computed / method,我們能在模板中保持簡潔,同時在邏輯層面完成複雜的樣式切換。
- 注意 避免在模板寫過長的表達式、正確使用變數作為 key、過濾 undefined,這些都是新手常踩的坑。
- 在實務專案裡,主題切換、表單驗證、動畫狀態 常會用到動態 class,熟練這些模式能讓你的 Vue 應用更具彈性與可維護性。
掌握了本文的概念與範例,你就能在 Vue3 中自如地管理 CSS,寫出既清晰又高效的前端程式碼。祝開發順利,持續探索 Vue 的無限可能!