本文 AI 產出,尚未審核

Vue 3 基礎概念 — 單檔元件(Single File Component, .vue)

簡介

在 Vue 生態系統中,**單檔元件(Single File Component,簡稱 SFC)**是最常見、也是最推薦的開發方式。它將 templatescriptstyle 三大部份封裝在同一個 .vue 檔案中,讓開發者可以在同一個檔案內完成 UI 標記、行為邏輯與樣式設計,提升可讀性、可維護性與團隊協作效率。

Vue 3 對 SFC 做了多項優化(如 <script setup><style scoped><template> 的編譯快取等),使得開發體驗更流暢、程式碼更簡潔。掌握 SFC 的寫法與最佳實踐,對於從 Vue 2 轉向 Vue 3,或是剛踏入前端框架的開發者,都相當重要。

本篇文章將以 淺顯易懂 的方式,從基本結構說起,逐步帶出實務上常用的技巧與陷阱,幫助你快速上手並寫出高品質的 Vue 3 單檔元件。


核心概念

1. SFC 的基本檔案結構

<!-- MyComponent.vue -->
<template>
  <!-- HTML 標記 -->
  <div class="card">
    <h2>{{ title }}</h2>
    <p>{{ message }}</p>
    <button @click="increment">點我 +1</button>
    <span>計數:{{ count }}</span>
  </div>
</template>

<script>
export default {
  name: 'MyComponent',          // 元件名稱
  props: {
    title: String,
    message: { type: String, default: 'Hello Vue 3!' }
  },
  data() {
    return {
      count: 0
    };
  },
  methods: {
    increment() {
      this.count++;
    }
  }
};
</script>

<style scoped>
.card {
  padding: 1rem;
  border: 1px solid #ddd;
  border-radius: 4px;
}
button {
  margin-top: 0.5rem;
}
</style>
  • <template>:放置 HTML 結構,支援所有 Vue 模板語法。
  • <script>:寫元件的 JavaScript(或 TypeScript)邏輯。
  • <style>:元件專屬的 CSS,使用 scoped 可避免樣式洩漏。

Tip:在同一個 .vue 檔案內,只能有一個 <template>、一個 <script>(或多個 <script setup>)以及任意多個 <style>


2. <script setup>:最簡潔的組件寫法

Vue 3.2 之後推出的 <script setup> 語法,讓開發者不必再寫 export default {},直接在檔案頂層宣告變數、函式,即可自動成為組件的 setup 內容。

<!-- Counter.vue -->
<template>
  <button @click="increment">點我 +1({{ count }})</button>
</template>

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

// 定義響應式資料
const count = ref(0);

// 方法直接寫成函式
function increment() {
  count.value++;
}
</script>

<style scoped>
button {
  font-size: 1rem;
  padding: 0.5rem 1rem;
}
</style>
  • ref 用於建立基本類型的響應式資料。
  • reactive(稍後會介紹)則適合物件/陣列。
  • 所有在 <script setup> 中聲明的變數,都會自動在模板中可見,不需要 this

3. Props、Emits 與 TypeScript

在 SFC 中使用 definePropsdefineEmits,可以在 <script setup> 中聲明 傳入屬性(props)自訂事件(emits),結合 TypeScript 時更能提供型別安全。

<!-- Greeting.vue -->
<template>
  <h1 @click="sayHello">{{ name }},歡迎光臨!</h1>
</template>

<script setup lang="ts">
import { defineProps, defineEmits } from 'vue';

// Props 型別
interface Props {
  name: string;
  age?: number;
}
const props = defineProps<Props>();

// Emits 型別
const emit = defineEmits<{
  (e: 'greet', payload: string): void;
}>();

function sayHello() {
  emit('greet', `Hello, ${props.name}!`);
}
</script>

<style scoped>
h1 {
  cursor: pointer;
  color: #42b983;
}
</style>
  • lang="ts" 讓編輯器自動啟用 TypeScript 語法檢查。
  • definePropsdefineEmits 皆返回對應的型別,避免在使用時出現型別錯誤

4. Scoped CSS 與 CSS Modules

scoped 會在編譯階段為每個元素自動加上唯一的屬性選擇器,確保樣式僅作用於當前元件;若需要更嚴格的模組化管理,Vue 也支援 CSS Modules

<!-- Card.vue -->
<template>
  <section :class="$style.card">
    <slot />
  </section>
</template>

<script setup>
</script>

<style module>
.card {
  border: 1px solid #e0e0e0;
  padding: 1rem;
  border-radius: 6px;
}
</style>
  • 使用 $style 取得模組化的 class 名稱,不會與其他元件衝突
  • module 取代 scoped,提供更細緻的命名空間。

5. 動態匯入與 Lazy Loading

在大型專案中,按需載入 元件能顯著減少首屏載入時間。Vue 3 支援 defineAsyncComponent 或直接使用動態 import()

<!-- AsyncWrapper.vue -->
<template>
  <Suspense>
    <template #default>
      <LazyComponent />
    </template>
    <template #fallback>
      <div>載入中…</div>
    </template>
  </Suspense>
</template>

<script setup>
import { defineAsyncComponent } from 'vue';

const LazyComponent = defineAsyncComponent(() =>
  import('./HeavyComponent.vue')
);
</script>
  • Suspense 會在子元件尚未解決前顯示 fallback,提升使用者體驗。
  • defineAsyncComponent 內部會自動處理錯誤、重試等機制。

常見陷阱與最佳實踐

陷阱 說明 解決方案
忘記加 scoped 樣式會洩漏到全局,造成意外的 UI 變化。 <style> 標籤加上 scoped,或改用 CSS Modules。
<script setup> 中使用 this setup 內部沒有 this,會拋錯。 直接使用變數或函式名稱,或使用 ref.value 取得值。
Props 變更直接修改 Props 是只讀的,直接改會觸發 Vue 警告。 若需要本地化修改,使用 const local = ref(props.someProp)computed 包裝。
樣式衝突 多個元件使用相同 class 名稱,scoped 無法解決全局樣式。 使用 BEM 命名法或 CSS Modules,確保 class 唯一性。
過度使用 v-if 在同一層級大量 v-if 會導致頻繁的虛擬 DOM 重建。 優先使用 v-show(僅切換 display)或把條件抽離到子元件。

最佳實踐

  1. 統一使用 <script setup>:除非需要特殊的 Options API,否則建議全專案採用此語法,減少樣板程式碼。
  2. 將大型元件拆分為子 SFC:保持每個 .vue 檔案的行數在 200 行以下,易於閱讀與測試。
  3. 使用 TypeScript:即使是小型專案,也能在開發階段捕捉錯誤,提升程式碼品質。
  4. 為每個元件寫測試:Vue Test Utils 搭配 Jest 可以對單檔元件的 props、emit、渲染結果進行單元測試。
  5. 遵循命名規範:檔案名稱使用 PascalCase(MyButton.vue),組件名稱同樣使用 PascalCase,方便自動匯入。

實際應用場景

1. 表單元件庫

在企業內部開發 表單元件庫 時,每個表單欄位(如 TextInput.vueDatePicker.vue)都可以是獨立的 SFC,透過 props 接收驗證規則,emit 回傳值變更。使用 <script setup> 可讓每個元件只保留必要的邏輯,減少重複程式碼。

2. 動態儀表板

儀表板往往需要根據使用者權限或資料量載入不同圖表。利用 動態匯入defineAsyncComponent)與 Suspense,可以在使用者切換頁籤時,僅載入當前需要的圖表元件,降低首屏資源消耗。

3. 多語系與主題切換

將多語系文字與主題樣式抽離成獨立的 SFC(如 LocaleProvider.vueThemeSwitcher.vue),配合 provide / injectPinia 狀態管理,讓整個應用在切換語系或主題時,只需要更新少數幾個元件,即可即時反映全局變化。


總結

Vue 3 的 單檔元件(.vue) 為前端開發提供了一個高度整合、易於維護的開發模式。透過 <template><script setup><style scoped>(或 CSS Modules)的協同工作,我們可以:

  • 快速建立 具備完整 UI、行為與樣式的元件。
  • 利用 TypeScript 提升型別安全,減少執行期錯誤。
  • 使用動態匯入Suspense,優化大型應用的載入效能。
  • 遵循最佳實踐(如 scoped、BEM、模組化 CSS)來避免常見的樣式與資料流問題。

熟練 SFC 的寫法,不僅能讓你在小型專案中快速迭代,也能在企業級應用中保持代碼的一致性與可測試性。現在就把上述概念套用到你的下一個 Vue 3 專案中,體驗「一檔搞定」的開發快感吧!祝開發順利 🎉