Vue3 Options API(傳統寫法)── Mixins 完全攻略
簡介
在 Vue.js 的開發過程中,程式碼重用 是提升開發效率與維護性的關鍵。Vue 3 仍然支援 Options API,而在 Options API 中最常被提及的重用機制,就是 mixins。mixins 允許我們把多個元件共用的 data、computed、methods、生命週期鉤子等抽離成獨立的模組,然後在需要的元件中「混入」使用。
對於剛接觸 Vue 的新手,mixins 可能看起來像是魔法般的「自動合併」行為;但只要弄清楚它的原理與限制,就能在 大型專案 中快速避免重複程式碼、統一行為(例如全局錯誤處理、表單驗證等)。本篇文章將從概念、實作、常見陷阱到最佳實踐,完整說明如何在 Vue 3 Options API 中正確運用 mixins,讓你在實務開發中如虎添翼。
核心概念
1. 什麼是 Mixins?
Mixins 本質上是一個普通的 JavaScript 物件,裡面可以定義 Vue 元件支援的任何選項(data、methods、computed、watch、生命週期鉤子…)。當一個元件 使用 mixins 屬性 時,Vue 會把 mixin 內的選項與元件本身的選項 合併,合併規則如下:
| 選項類型 | 合併方式 |
|---|---|
data |
兩者會被 深度合併,若同名屬性會被元件本身的值覆寫 |
methods、computed、watch |
以 鍵名為基準,若衝突,元件本身的定義會 覆蓋 mixin |
生命週期鉤子(created、mounted…) |
會 串接執行,先執行 mixin 再執行元件本身的鉤子 |
重點:mixins 只是一種「靜態」的程式碼組合方式,執行時不會產生任何額外的實例或代理(proxy)。
2. 建立與使用 Mixins
2.1 建立一個簡單的 mixin
// src/mixins/loggerMixin.js
export const loggerMixin = {
// 生命週期:在元件建立時印出訊息
created() {
console.log(`[Logger] ${this.$options.name} 已被建立`);
},
// 共用的方法
methods: {
/** 記錄訊息到 console,支援多種等級 */
log(message, level = 'info') {
const prefix = `[${level.toUpperCase()}]`;
console.log(`${prefix} ${message}`);
}
}
};
2.2 在元件中混入
// src/components/HelloWorld.vue
<script>
import { loggerMixin } from '@/mixins/loggerMixin';
export default {
name: 'HelloWorld',
mixins: [loggerMixin],
data() {
return {
greeting: 'Hello Vue 3!'
};
},
mounted() {
// 直接使用 mixin 中的 log 方法
this.log(this.greeting, 'debug');
}
};
</script>
在上例中,
created鉤子會先由loggerMixin執行,接著再執行HelloWorld本身(若有created)。log方法則直接掛在元件實例上供使用。
3. 多個 Mixins 與合併順序
Vue 允許在同一個元件中使用 多個 mixin,它們會依照陣列的順序依次合併。下面示範兩個 mixin 同時作用的情況:
// src/mixins/apiMixin.js
export const apiMixin = {
methods: {
fetchData(url) {
return fetch(url).then(res => res.json());
}
}
};
// src/mixins/validationMixin.js
export const validationMixin = {
methods: {
isEmail(str) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(str);
}
}
};
// src/components/UserForm.vue
<script>
import { apiMixin } from '@/mixins/apiMixin';
import { validationMixin } from '@/mixins/validationMixin';
import { loggerMixin } from '@/mixins/loggerMixin';
export default {
name: 'UserForm',
mixins: [loggerMixin, apiMixin, validationMixin],
data() {
return {
email: '',
userData: null
};
},
methods: {
submit() {
if (!this.isEmail(this.email)) {
this.log('Email 格式錯誤', 'error');
return;
}
this.fetchData(`/api/user?email=${this.email}`)
.then(data => {
this.userData = data;
this.log('使用者資料已取得', 'info');
});
}
}
};
</script>
說明:
loggerMixin、apiMixin、validationMixin依序混入,若它們各自都有同名的created鉤子,執行順序會是loggerMixin → apiMixin → validationMixin → UserForm。
4. 透過 this 存取 Mixins 內的屬性
在 Options API 中,mixins 內的 data、computed、methods、props 都會掛在同一個 Vue 實例上,直接使用 this 即可存取:
export const timerMixin = {
data() {
return {
seconds: 0,
timerId: null
};
},
created() {
this.startTimer();
},
beforeUnmount() {
clearInterval(this.timerId);
},
methods: {
startTimer() {
this.timerId = setInterval(() => {
this.seconds++;
}, 1000);
}
}
};
// 任意元件
<script>
import { timerMixin } from '@/mixins/timerMixin';
export default {
name: 'Clock',
mixins: [timerMixin],
template: `<div>已經運行 {{ seconds }} 秒</div>`
};
</script>
這個範例展示了 狀態共享(
seconds)以及 生命週期管理(created、beforeUnmount)皆可透過 mixin 完成,元件本身只需要關注 UI。
5. 何時使用 Mixins vs. Composition API
雖然 Vue 3 推薦使用 Composition API 取代 mixins,仍有以下情況適合保留 mixins:
| 使用情境 | 為什麼選擇 mixins |
|---|---|
| 既有大型 Options API 專案 | 直接在現有結構上加入重用模組,成本最低 |
| 跨多個元件的簡單功能(如全局 logger、簡易驗證) | 只需要少量 methods/created,不必建立完整的 composable |
| 第三方套件提供的 mixin | 某些套件(例如 Vue Router v2、Vuex)仍以 mixin 方式擴充功能 |
程式碼範例
以下提供 五個實務上常見 的 mixin 範例,從基礎到稍微進階,幫助你快速上手。
範例 1️⃣ 全局錯誤處理 Mixin
// src/mixins/errorHandlerMixin.js
export const errorHandlerMixin = {
data() {
return {
errorMessage: ''
};
},
methods: {
/** 捕捉錯誤並顯示於 UI */
handleError(err) {
console.error('[Error]', err);
this.errorMessage = err.message || '發生未知錯誤';
},
/** 清除錯誤訊息 */
clearError() {
this.errorMessage = '';
}
}
};
在任何需要捕捉例外的元件,只要
mixins: [errorHandlerMixin],就能直接呼叫this.handleError(err),而 UI 只要綁定errorMessage即可。
範例 2️⃣ 表單驗證 Mixin(支援多欄位)
// src/mixins/formValidateMixin.js
export const formValidateMixin = {
data() {
return {
// 以欄位名稱為鍵,值為驗證結果
validation: {}
};
},
methods: {
/** 設定欄位驗證結果 */
setFieldValidity(field, isValid, message = '') {
this.$set(this.validation, field, { isValid, message });
},
/** 檢查所有欄位是否都通過驗證 */
isFormValid() {
return Object.values(this.validation).every(v => v.isValid);
}
}
};
<!-- 在表單元件中使用 -->
<script>
import { formValidateMixin } from '@/mixins/formValidateMixin';
export default {
name: 'RegisterForm',
mixins: [formValidateMixin],
data() {
return {
username: '',
password: ''
};
},
methods: {
validateUsername() {
const ok = this.username.length >= 4;
this.setFieldValidity('username', ok, ok ? '' : '至少 4 個字元');
},
validatePassword() {
const ok = this.password.length >= 6;
this.setFieldValidity('password', ok, ok ? '' : '至少 6 個字元');
},
submit() {
this.validateUsername();
this.validatePassword();
if (this.isFormValid()) {
// 送出表單
}
}
}
};
</script>
範例 3️⃣ 跨頁面的 Loading 狀態 Mixin
// src/mixins/loadingMixin.js
export const loadingMixin = {
data() {
return {
isLoading: false
};
},
methods: {
/** 包裝 async 函式,使其自動切換 loading */
async withLoading(promiseFn) {
this.isLoading = true;
try {
const result = await promiseFn();
return result;
} finally {
this.isLoading = false;
}
}
}
};
// 使用方式
import { loadingMixin } from '@/mixins/loadingMixin';
export default {
name: 'ArticleList',
mixins: [loadingMixin],
data() {
return { articles: [] };
},
async created() {
await this.withLoading(async () => {
this.articles = await fetch('/api/articles').then(r => r.json());
});
}
};
範例 4️⃣ 事件總線 (Event Bus) Mixin
注意:在 Vue 3 中已不建議使用全局事件總線,但在遺留系統或簡易原型仍可透過 mixin 快速實作。
// src/mixins/eventBusMixin.js
import { mitt } from 'mitt'; // npm i mitt
const emitter = mitt();
export const eventBusMixin = {
methods: {
$emitEvent(event, payload) {
emitter.emit(event, payload);
},
$onEvent(event, handler) {
emitter.on(event, handler);
},
$offEvent(event, handler) {
emitter.off(event, handler);
}
},
beforeUnmount() {
// 移除所有註冊的事件(防止記憶體洩漏)
emitter.all.clear();
}
};
// A.vue
export default {
mixins: [eventBusMixin],
mounted() {
this.$onEvent('login-success', user => {
console.log('收到登入成功通知', user);
});
}
};
// B.vue
export default {
mixins: [eventBusMixin],
methods: {
login() {
// 假設登入成功後
const user = { name: 'Alice' };
this.$emitEvent('login-success', user);
}
}
};
範例 5️⃣ 多語系 (i18n) Helper Mixin
// src/mixins/i18nMixin.js
export const i18nMixin = {
methods: {
/** 依照目前語系取得對應文字 */
$t(key) {
// 假設有全局的 i18n 物件
return this.$root.$i18n ? this.$root.$i18n.t(key) : key;
}
}
};
<!-- 任意元件 -->
<template>
<button>{{ $t('button.save') }}</button>
</template>
<script>
import { i18nMixin } from '@/mixins/i18nMixin';
export default {
name: 'SaveButton',
mixins: [i18nMixin]
};
</script>
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 / 最佳實踐 |
|---|---|---|
| 名稱衝突 | Mixins 與元件本身同名的 data、methods、computed 會被元件覆寫,導致行為不易追蹤。 |
命名規範:在 mixin 中使用前綴(如 mixinXxx)或將功能拆成多個小 mixin。 |
| 生命週期執行順序不明 | 多個 mixin 同時提供同一生命週期鉤子,執行順序依陣列順序,容易產生隱蔽的副作用。 | 明確排序:將重要的 side‑effect 放在最後一個 mixin,或在鉤子內自行調整執行順序。 |
| 過度依賴全域狀態 | 在 mixin 中直接使用 this.$store、this.$router,會讓 mixin 變成「耦合」的黑盒子。 |
注入式設計:將需要的依賴作為參數傳入(例如 init(store)),或使用 Composition API 重構。 |
| 測試困難 | Mixins 隱藏在元件背後,單元測試時必須額外載入或 mock。 | 分離測試:為每個 mixin 撰寫獨立的測試檔,並在元件測試中只驗證 UI 行為。 |
| 記憶體洩漏 | 在 mixin 中註冊 setInterval、event listeners,但未在 beforeUnmount 清除。 |
在生命週期內清理:一定在 beforeUnmount(或 unmounted)裡釋放資源。 |
最佳實踐總結
- 保持單一職責:每個 mixin 只解決一件事(例如「錯誤處理」或「Loading」),避免變成「萬用工具箱」。
- 使用前綴或命名空間:減少名稱衝突,例如
logInfo→mixinLogInfo。 - 盡量在
created/mounted前完成資料初始化,避免在mounted之後才執行重要邏輯。 - 在大型專案中逐步遷移到 Composition API:可以先保留核心的 mixin,未來再以 composable 替代。
- 文件化每個 mixin:在專案的
docs/mixins/目錄下建立說明文件,列出提供的屬性、使用方式與注意事項。
實際應用場景
| 場景 | 典型需求 | 為何選擇 Mixins |
|---|---|---|
| 全局 API 錯誤攔截 | 所有 AJAX 請求失敗時顯示統一的錯誤提示。 | 只要在 errorHandlerMixin 中提供 handleError,所有元件只要混入即可。 |
| 多頁面表單共用驗證 | 登入、註冊、忘記密碼等表單需相同的欄位驗證邏輯。 | formValidateMixin 把驗證規則抽離,減少重複程式碼。 |
| 長輪詢或即時資料 | 多個元件需要每 5 秒自動刷新資料。 | timerMixin 可在 created 中啟動計時器,在 beforeUnmount 中自動清除。 |
| 全局 Loading 指示 | 在任何非同步操作期間顯示全局 loading spinner。 | loadingMixin 的 withLoading 包裝所有 async 呼叫,統一管理 UI。 |
| 跨元件訊息傳遞 | 不同層級的元件需要互相通知(例如「使用者已登入」)。 | eventBusMixin 讓元件以事件方式解耦溝通。 |
總結
Mixins 是 Vue 3 Options API 中 最直觀、最快速 的程式碼重用手段。透過將共用的 data、methods、computed、生命週期鉤子等抽離成獨立模組,我們可以:
- 減少重複程式碼,提升維護效率。
- 統一行為(如錯誤處理、Loading、表單驗證)。
- 在既有 Options API 專案中快速導入,不必一次性改寫為 Composition API。
然而,mixins 也有 名稱衝突、執行順序不易掌控 等缺點,使用時必須遵守命名規範、保持單一職責、在生命週期內妥善清理資源。對於大型或長期維護的專案,建議逐步將關鍵功能遷移至 Composable(Composition API)以獲得更好的型別支援與可測試性。
掌握了本文的概念與範例,你就能在 Vue 3 Options API 中自信地運用 mixins,為專案帶來更乾淨、更具可讀性的程式碼結構。祝開發順利,持續寫出高品質的 Vue 應用!