Vue3 – 樣式與 CSS 管理
主題:CSS Module
簡介
在大型前端專案中,樣式衝突是最常見的痛點之一。傳統的全域 CSS 容易因為命名不當或檔案載入順序而產生意外的樣式覆寫,讓維護成本急速上升。Vue3 提供的 CSS Module 機制,能自動將每個組件的樣式限制在該組件的作用域內,讓開發者可以以 模組化、可預測 的方式管理 CSS。
本篇文章將從概念說明、實作範例、常見陷阱到最佳實踐,完整介紹在 Vue3 中如何使用 CSS Module,協助你在真實專案中快速上手、降低樣式衝突的風險。
核心概念
1. CSS Module 是什麼?
CSS Module 本質上是一套 編譯時 的 CSS 處理規則。當 Vue 的單檔組件(.vue)中使用 <style module> 時,編譯器會:
- 為每個 CSS 選擇器產生一個 唯一的 hash 名稱(如
button_1a2b3c)。 - 把這個 hash 注入到組件的
$style物件中,供模板使用。 - 只在此組件渲染的 DOM 上套用這些經過 hash 的 class,避免全域污染。
優點:
- 完全避免樣式衝突
- 支援局部變數、
@import、@keyframes等完整 CSS 功能- 與 Vue 的
<script setup>、Composition API 完美結合
2. 在 Vue3 中啟用 CSS Module
在 Vue CLI、Vite 或 Nuxt 等工具鏈中,預設已支援 CSS Module,只要在 <style> 標籤加上 module 屬性即可。
<!-- MyButton.vue -->
<template>
<button :class="$style.btn">{{ label }}</button>
</template>
<script setup>
defineProps({ label: String })
</script>
<style module>
.btn {
padding: 0.5rem 1rem;
background-color: #42b983;
color: #fff;
border: none;
border-radius: 4px;
}
</style>
注意:
$style只在<template>中可直接使用;在<script>中若需要動態取得 class,可透過import或useCssModule()。
3. 取得 $style 的方式
| 方式 | 使用情境 | 範例 |
|---|---|---|
直接在模板 ($style) |
靜態 class 綁定 | :class="$style.container" |
useCssModule() (Composition API) |
在 <script setup> 或普通 <script> 中使用 |
const styles = useCssModule(); |
import styles from './Component.module.css' (Vite) |
使用外部 CSS Module 檔案 | import styles from './button.module.css' |
範例:在 <script setup> 中使用 useCssModule
<template>
<div :class="styles.wrapper">
<p :class="styles.text">Hello CSS Module!</p>
</div>
</template>
<script setup>
import { useCssModule } from 'vue'
const styles = useCssModule()
</script>
<style module>
.wrapper {
padding: 1rem;
background: #f5f5f5;
}
.text {
color: #333;
font-weight: bold;
}
</style>
4. 多個 CSS Module 及自訂名稱
如果一個組件需要 多個 CSS Module(例如同時使用 UI library 與自訂樣式),可以給 module 加上自訂名稱:
<style module="ui">
/* UI library 的樣式 */
.button {
/* ... */
}
</style>
<style module="local">
/* 自己的樣式 */
.title {
/* ... */
}
</style>
<template>
<button :class="[ui.button, local.title]">混合樣式</button>
</template>
這樣在模板中就會產生兩個物件
ui、local,分別對應不同的 CSS Module。
5. 實用範例
以下提供 四個實務範例,示範不同情境下的 CSS Module 用法。每個範例都附上說明與重點提示。
範例 1:基本的 Button 組件
<!-- src/components/BaseButton.vue -->
<template>
<button :class="$style.btn" @click="$emit('click')">
<slot>按鈕</slot>
</button>
</template>
<script setup>
defineEmits(['click'])
</script>
<style module>
.btn {
display: inline-block;
padding: 0.6rem 1.2rem;
background: #42b983;
color: #fff;
border-radius: 4px;
cursor: pointer;
transition: opacity 0.2s;
}
.btn:hover {
opacity: 0.9;
}
</style>
重點:使用
$style.btn直接綁定,保證此按鈕的樣式不會被其他全域.btn影響。
範例 2:結合外部 CSS Module 檔案
/* src/assets/styles/card.module.scss */
.card {
border: 1px solid #e0e0e0;
border-radius: 6px;
padding: 1rem;
background: #fff;
}
.title {
font-size: 1.25rem;
margin-bottom: 0.5rem;
}
<!-- src/components/Card.vue -->
<template>
<div :class="styles.card">
<h3 :class="styles.title"><slot name="title"/></h3>
<div><slot/></div>
</div>
</template>
<script setup>
import styles from '@/assets/styles/card.module.scss'
</script>
說明:Vite 直接支援
.module.scss,只要import即可取得 hash 後的 class 名稱。
範例 3:使用 useCssModule 動態切換樣式
<template>
<div :class="currentClass">切換顏色</div>
<button @click="toggle">切換</button>
</template>
<script setup>
import { ref } from 'vue'
import { useCssModule } from 'vue'
const styles = useCssModule()
const isRed = ref(true)
const currentClass = computed(() => (isRed.value ? styles.red : styles.blue))
function toggle() {
isRed.value = !isRed.value
}
</script>
<style module>
.red {
color: #e53935;
}
.blue {
color: #1e88e5;
}
</style>
技巧:
computed搭配$style(或styles)可在執行時切換不同的樣式,仍保持模組化的安全性。
範例 4:多個 Module 與自訂名稱
<template>
<nav :class="[layout.nav, theme.nav]">
<a :class="layout.link" href="#">首頁</a>
<a :class="layout.link" href="#">關於</a>
</nav>
</template>
<script setup>
import { useCssModule } from 'vue'
const layout = useCssModule('layout')
const theme = useCssModule('theme')
</script>
<style module="layout">
.nav {
display: flex;
gap: 1rem;
}
.link {
text-decoration: none;
color: inherit;
}
</style>
<style module="theme">
.nav {
background: #212121;
padding: 0.5rem;
}
</style>
重點:同名的 CSS class(如
.nav)在不同 Module 中會產生不同的 hash,互不干擾,讓 UI 結構與主題樣式可以分離管理。
常見陷阱與最佳實踐
| 陷阱 | 可能原因 | 解決方式 / 最佳實踐 |
|---|---|---|
樣式找不到 (undefined is not an object (reading '$style')) |
<style> 沒加 module 或檔案命名錯誤 |
確認 <style module> 或 <style module="name"> 正確寫入;若使用外部 .module.css,檔名必須包含 .module. |
| 全域 CSS 仍然影響 | 使用了 深度選擇子(如 >>>、/deep/)或 ::v-deep |
盡量避免深度選擇子;若必須,改用 CSS Module + BEM 的命名方式,或把需要的全域樣式搬到另一個 Module |
| Hash 名稱衝突(在開發版看到相同的 class) | 懶惰載入(dynamic import)導致同一檔案被多次編譯 | 使用 vite-plugin-css-modules 或 vue-cli 的預設配置,保證每個檔案只被編譯一次 |
@keyframes 無效 |
keyframes 名稱未被 hash,導致全域衝突 | 在 Module 中使用 :global 標記或直接在 <style>(非 module)裡定義 keyframes,再在 Module 中引用 |
在 TypeScript 中取得 $style |
TypeScript 無法推斷 $style 型別 |
在 shims-vue.d.ts 中加入 declare module '*.module.css' { const classes: Record<string, string>; export default classes; } 以及 declare module '*.module.scss' { ... } |
最佳實踐
- 盡量讓每個組件只保有一個
<style module>,避免混用全域與模組樣式。 - 使用 BEM 命名法結合 CSS Module(如
button__icon),即使在編譯後變成button__icon_1a2b3c,可提升可讀性。 - 把共用變數、mixins、reset 放在全域樣式(
src/assets/styles/global.css),其餘皆走 Module。 - 在 Vite 中啟用
css.modules設定,可自訂產生的 hash 格式(如[name]__[local]___[hash:base64:5]),方便除錯。 - 測試時檢查產生的 class:開發者工具中會看到
button_1a2b3c,若看到未 hash 的名稱,代表該樣式未被 Module 包裹。
實際應用場景
| 場景 | 為何選擇 CSS Module |
|---|---|
| 大型企業內部系統 | 多個開發團隊同時維護不同功能模組,避免樣式相互覆寫。 |
| 設計系統(Design System) | 每個 UI 元件(Button、Card、Modal)都有獨立的樣式模組,方便版本管理與發佈。 |
| 多主題切換(Dark / Light) | 主題樣式放在一個獨立的 Module,透過動態切換 theme Module 內的 class,保持組件本身的樣式不變。 |
| SSR(Server‑Side Rendering) | CSS Module 在編譯階段就產生唯一 class,SSR 時不會因為樣式先後順序產生 FOUC(Flash of Unstyled Content)。 |
| Vue3 + Vite + TypeScript | Vite 原生支援 .module.css/.module.scss,配合 TypeScript 的型別聲明,可在 IDE 中即時提示可用的 class 名稱。 |
案例簡述:某電商平台的商品卡片(
ProductCard.vue)使用 CSS Module 管理卡片的排版、陰影與按鈕樣式,並在ThemeSwitcher.vue中動態載入dark.module.css與light.module.css,完成即時主題切換,且不會因為其他開發者新增全域.card樣式而破壞原有排版。
總結
- CSS Module 為 Vue3 提供了 模組化、唯一性的樣式管理,有效解決全域 CSS 的衝突問題。
- 只要在
<style>標籤加上module,或使用.module.css/.module.scss檔案,即可自動取得$style(或useCssModule())物件。 - 透過 多個 Module、命名自訂、
useCssModule等技巧,可在同一組件內同時管理結構樣式、主題樣式與第三方 UI 樣式。 - 常見的陷阱包括忘記加
module、深度選擇子造成全域污染、@keyframes未被 hash 等,依照本文提供的解決方案即可快速排除。 - 在實務專案中,將 共用變數/全域重置 放在全域樣式,將 元件專屬樣式 放入 CSS Module,配合 BEM 命名 與 自訂 hash 格式,即可建立易維護、可擴充的樣式架構。
掌握 CSS Module 後,你的 Vue3 專案將能在 樣式安全性、開發效率 與 可維護性 上得到顯著提升,從此不再為樣式衝突而頭痛。祝開發順利!