本文 AI 產出,尚未審核

Vue 3 基礎概念:設計理念(Composition API、Tree‑shaking)


簡介

Vue.js 從 2.x 版開始就以「易上手、彈性高」聞名,而在 2020 年正式釋出的 Vue 3,更以 Composition API 為核心,徹底改變了開發者組織程式碼的方式。除了提升可讀性與可維護性,Vue 3 同時支援 tree‑shaking,讓最終打包的檔案只保留真正被使用的程式碼,顯著減少 bundle 大小。

對於 初學者 來說,了解這兩個概念不僅能快速上手新專案,還能在日後的大型應用中避免「程式碼難以維護」的痛點;對 中級開發者,則是優化效能、降低維護成本的關鍵武器。以下,我將以簡潔易懂的方式,說明 Composition API 與 tree‑shaking 的設計理念、實作方式與常見陷阱,並提供實務範例供讀者直接套用。


核心概念

1. 為什麼需要 Composition API?

在 Vue 2 中,我們主要使用 Options APIdatamethodscomputedwatch…)來定義組件。當組件功能變得複雜時,相關的程式碼往往散落在多個選項裡,導致:

  • 邏輯分散:相同功能的程式碼在不同生命週期鉤子中交錯。
  • 重複程式碼:多個組件需要相同的功能時,往往要 copy‑paste。
  • 類型推斷困難:在 TypeScript 中,Options API 的型別推斷較為有限。

Composition API 透過 setup() 函式,將相關邏輯聚合在一起,形成「可重用的功能單元」——Composable。這種寫法不僅更符合函式式編程的思維,也讓 TypeScript 能夠更精準地推斷型別。

1.1 基本語法

import { ref, computed, watch } from 'vue'

export default {
  // Vue 3 必須提供的選項
  name: 'Counter',
  // 所有邏輯都寫在這裡
  setup() {
    const count = ref(0)               // 响應式資料
    const double = computed(() => count.value * 2) // 計算屬性

    function increment() {
      count.value++
    }

    // 監聽 count 變化
    watch(count, (newVal, oldVal) => {
      console.log(`count 從 ${oldVal} 變成 ${newVal}`)
    })

    // 必須回傳給模板使用的屬性/方法
    return { count, double, increment }
  }
}

重點setup() 只會在組件 首次 初始化時執行一次,之後的更新皆由 Vue 自動追蹤 refreactive 等響應式物件。


2. 可重用的 Composable

Composable 本質上是 一個普通的 JavaScript 函式,它可以返回任何響應式資料或方法。利用這個特性,我們可以把「計數器」的邏輯抽離成獨立模組,供多個組件共用。

2.1 範例:useCounter.js

// src/composables/useCounter.js
import { ref, computed } from 'vue'

export function useCounter(initial = 0) {
  const count = ref(initial)
  const double = computed(() => count.value * 2)

  function increment(step = 1) {
    count.value += step
  }

  function decrement(step = 1) {
    count.value -= step
  }

  return { count, double, increment, decrement }
}

2.2 在組件中使用

// CounterA.vue
import { useCounter } from '@/composables/useCounter'

export default {
  name: 'CounterA',
  setup() {
    const { count, double, increment } = useCounter(5)
    return { count, double, increment }
  }
}
// CounterB.vue
import { useCounter } from '@/composables/useCounter'

export default {
  name: 'CounterB',
  setup() {
    const { count, decrement } = useCounter(10)
    return { count, decrement }
  }
}

技巧:把「狀態」與「行為」放在同一個 composable 中,讓組件只負責「呈現」與「組合」的工作。


3. Tree‑shaking:只打包你真的用到的程式碼

Tree‑shaking 是 ES6 模組化帶來的副作用:當打包工具(如 Vite、Webpack)偵測到某個模組的 export 從未被引用,就會在最終的 bundle 中把它「砍掉」。Vue 3 從底層就採用了 ESM(ECMAScript Modules),配合 rollupesbuild,可以自動完成這項工作。

3.1 為何要關注 Tree‑shaking?

  • 減少檔案大小:只載入必要的功能,提升首次渲染速度。
  • 降低記憶體占用:未使用的程式碼不會被解析,減少瀏覽器的記憶體開銷。
  • 更易維護:開發者自然會傾向只匯入需要的 API,避免「全部引入」的壞習慣。

3.2 正確的匯入方式

// ❌ 錯誤:一次匯入所有 Vue API,會讓 tree‑shaking 失效
import Vue from 'vue'

// ✅ 正確:只匯入實際需要的函式
import { ref, computed } from 'vue'

如果你使用 Vue RouterVuex(Vue 3 中的 Pinia)等套件,同樣要遵守「只匯入需要的模組」原則:

// 只匯入 createRouter 與 createWebHistory,其他功能不會被打包
import { createRouter, createWebHistory } from 'vue-router'

3.3 針對第三方套件的 Tree‑shaking

許多 UI 庫(如 Element Plus、Naive UI)都提供了 按需引入 的方式。例如使用 Element Plus:

// main.js
import { createApp } from 'vue'
import App from './App.vue'

// 按需引入 ElButton,其他元件不會被載入
import { ElButton } from 'element-plus'
import 'element-plus/lib/theme-chalk/el-button.css'

const app = createApp(App)
app.component('ElButton', ElButton)
app.mount('#app')

提醒:若使用 babel-plugin-importunplugin-vue-components 等自動化插件,記得在 vite.config.tswebpack.config.js 中正確設定,否則仍可能因「全局引入」而失去 tree‑shaking 效益。


4. 其他常見的 Composition API API

API 用途 範例
ref 建立單一值的響應式引用 const name = ref('Vue')
reactive 建立深層物件的響應式 const state = reactive({ count: 0 })
computed 基於其他響應式資料的衍生值 const doubled = computed(() => state.count * 2)
watch 監聽單一或多個來源的變化 watch(() => state.count, (newV) => console.log(newV))
watchEffect 自動追蹤依賴的副作用 watchEffect(() => console.log(state.count))
provide / inject 跨層級傳遞資料(取代 Vuex 的簡易情境) provide('theme', ref('dark')) / const theme = inject('theme')

常見陷阱與最佳實踐

陷阱 說明 解決方案
setup 之外使用 ref ref 必須在 setup 或其他 composable 中調用,否則無法被 Vue 追蹤。 把所有響應式宣告都放在 setup 或自訂 composable 中。
過度抽象 把過於細小的邏輯抽成 composable,導致檔案過多、閱讀成本上升。 功能完整性(如「表單驗證」)劃分 composable,避免過度切割。
忘記返回 setup 中宣告的變數若未回傳,模板無法直接使用。 確認 return { … } 包含所有需要的屬性/方法。
Tree‑shaking 失效 使用 import Vue from 'vue'import * as Vue from 'vue' 會把整個套件拉進 bundle。 僅匯入實際使用的 API(如 refcomputed),或使用 viteoptimizeDeps 設定。
watch 依賴過深 直接監聽大型物件(watch(state, …))會導致不必要的觸發。 使用 watch(() => state.prop, …)deep: true 只在需要時開啟深層監聽。

最佳實踐

  1. 以功能為單位建立 composable,例如 useForm, useFetch, useAuth
  2. 保持 setup 簡潔:只負責呼叫 composable、回傳資料。
  3. 使用 TypeScript:在 composable 中加入明確的返回型別,提升 IDE 補全與錯誤捕捉。
  4. 檢查打包結果:執行 vite build --mode production,使用 source-mapwebpack-bundle-analyzer 確認 tree‑shaking 生效。

實際應用場景

1. 大型企業儀表板

  • 需求:同一套 UI 需要多個圖表、篩選條件、即時資料更新。
  • 解法:將圖表資料抓取、篩選邏輯抽成 useChartData,表單驗證抽成 useValidator,每個子組件只負責呈現。透過 tree‑shaking,只打包實際使用到的圖表套件(如 echarts 的特定模組),減少首次載入時間。

2. 手機端 PWA(Progressive Web App)

  • 需求:流量受限的行動裝置,需要盡可能小的 bundle。
  • 解法:使用 Composition API 把離線快取、推播訂閱等功能寫成 usePWA,在不同頁面只匯入需要的功能。配合 Vite 的 esbuild,確保未使用的 API 完全被剔除。

3. 多語系、主題切換的 SaaS 平台

  • 需求:全站支援暗黑模式、語系切換,且每個子模組都可能需要存取設定。
  • 解法:利用 provide/inject 搭配 useSettings composable,讓所有子組件都能即時取得最新設定,同時保持 單向依賴,避免全局 store 帶來的額外負擔。只匯入 refcomputedwatch,其餘 Vue 內部工具不會進入 bundle。

總結

Vue 3 的 Composition APItree‑shaking 兩大設計理念,從根本上改變了我們撰寫 Vue 應用的方式:

  • Composition API 讓程式碼以功能為單位聚合,提升可重用性、可測試性,並在 TypeScript 環境下取得更好的型別支援。
  • Tree‑shaking 則確保最終產出的 bundle 只包含實際使用到的程式碼,對效能與資源使用都有顯著的正向影響。

掌握這兩者的核心概念與實作技巧,能讓你在 從小型專案到大型企業級應用 的開發過程中,保持程式碼的乾淨、效能的最優化,以及維護成本的可控。未來隨著 Vue 生態系統持續演進,這些設計理念將會成為前端開發者不可或缺的基礎能力。

實務建議:在新專案起手式就使用 vite + Vue 3,以 ESM 為主,搭配 自動化的按需引入插件(如 unplugin-vue-components),讓 Composition API 與 tree‑shaking 從一開始就發揮最大效益。祝開發順利,玩得開心!