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 API(data、methods、computed、watch…)來定義組件。當組件功能變得複雜時,相關的程式碼往往散落在多個選項裡,導致:
- 邏輯分散:相同功能的程式碼在不同生命週期鉤子中交錯。
- 重複程式碼:多個組件需要相同的功能時,往往要 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 自動追蹤ref、reactive等響應式物件。
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),配合 rollup 或 esbuild,可以自動完成這項工作。
3.1 為何要關注 Tree‑shaking?
- 減少檔案大小:只載入必要的功能,提升首次渲染速度。
- 降低記憶體占用:未使用的程式碼不會被解析,減少瀏覽器的記憶體開銷。
- 更易維護:開發者自然會傾向只匯入需要的 API,避免「全部引入」的壞習慣。
3.2 正確的匯入方式
// ❌ 錯誤:一次匯入所有 Vue API,會讓 tree‑shaking 失效
import Vue from 'vue'
// ✅ 正確:只匯入實際需要的函式
import { ref, computed } from 'vue'
如果你使用 Vue Router、Vuex(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-import、unplugin-vue-components等自動化插件,記得在 vite.config.ts 或 webpack.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(如 ref、computed),或使用 vite 的 optimizeDeps 設定。 |
| watch 依賴過深 | 直接監聽大型物件(watch(state, …))會導致不必要的觸發。 |
使用 watch(() => state.prop, …) 或 deep: true 只在需要時開啟深層監聽。 |
最佳實踐:
- 以功能為單位建立 composable,例如
useForm,useFetch,useAuth。 - 保持
setup簡潔:只負責呼叫 composable、回傳資料。 - 使用 TypeScript:在 composable 中加入明確的返回型別,提升 IDE 補全與錯誤捕捉。
- 檢查打包結果:執行
vite build --mode production,使用source-map或webpack-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搭配useSettingscomposable,讓所有子組件都能即時取得最新設定,同時保持 單向依賴,避免全局 store 帶來的額外負擔。只匯入ref、computed、watch,其餘 Vue 內部工具不會進入 bundle。
總結
Vue 3 的 Composition API 與 tree‑shaking 兩大設計理念,從根本上改變了我們撰寫 Vue 應用的方式:
- Composition API 讓程式碼以功能為單位聚合,提升可重用性、可測試性,並在 TypeScript 環境下取得更好的型別支援。
- Tree‑shaking 則確保最終產出的 bundle 只包含實際使用到的程式碼,對效能與資源使用都有顯著的正向影響。
掌握這兩者的核心概念與實作技巧,能讓你在 從小型專案到大型企業級應用 的開發過程中,保持程式碼的乾淨、效能的最優化,以及維護成本的可控。未來隨著 Vue 生態系統持續演進,這些設計理念將會成為前端開發者不可或缺的基礎能力。
實務建議:在新專案起手式就使用
vite+Vue 3,以 ESM 為主,搭配 自動化的按需引入插件(如unplugin-vue-components),讓 Composition API 與 tree‑shaking 從一開始就發揮最大效益。祝開發順利,玩得開心!