本文 AI 產出,尚未審核

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 結構。

Tipv-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>

說明isActiveref,每次點擊會切換布林值,active class 會依值自動加減。

範例 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 寫長長的表達式會降低可讀性。此時可將邏輯抽到 computedmethod 中,回傳符合 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,讓每個連結根據當前頁面自動加上 activenavClass 使用 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 雜亂、難以除錯。 把邏輯搬到 computedmethods,保持 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()

最佳實踐

  1. 盡量使用物件語法:可讀性最高,條件明確。
  2. 將複雜邏輯抽離:使用 computed 產生最終的 class 物件或陣列。
  3. 保持 class 命名一致:建議採用 BEM 或 CSS Modules,避免全局污染。
  4. 結合 CSS 變數:動態 class 只負責切換狀態,樣式細節交給 CSS 變數管理,提升可維護性。
  5. 測試渲染結果:開發時可在 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>

重點:只要 emailErrortrueis-invalid class 即被加入,樣式自動切換。

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>

說明rotating class 控制動畫,show 控制是否渲染,兩者可獨立切換,展示了動態 class 與 Vue transition 的結合。


總結

  • 動態 class 綁定 是 Vue3 中最常用且最具威力的 UI 控制手段之一。
  • 透過 物件語法、陣列語法 以及 computed / method,我們能在模板中保持簡潔,同時在邏輯層面完成複雜的樣式切換。
  • 注意 避免在模板寫過長的表達式、正確使用變數作為 key、過濾 undefined,這些都是新手常踩的坑。
  • 在實務專案裡,主題切換、表單驗證、動畫狀態 常會用到動態 class,熟練這些模式能讓你的 Vue 應用更具彈性與可維護性。

掌握了本文的概念與範例,你就能在 Vue3 中自如地管理 CSS,寫出既清晰高效的前端程式碼。祝開發順利,持續探索 Vue 的無限可能!