Vue3 Options API(傳統寫法)— watch 完全指南
簡介
在 Vue3 中,Options API 仍然是許多既有專案與新手入門的首選寫法。除了 data、computed、methods,watch(監聽器)是讓我們在資料變化時執行副作用的強大工具。無論是表單驗證、API 請求、或是同步 UI 與瀏覽器 URL,適當使用 watch 能讓程式碼更具可讀性與可維護性。
本篇文章將從 概念、實作範例、常見陷阱、最佳實踐 以及 實務應用場景 逐層剖析,讓你在 Vue3 Options API 中自信地使用 watch,從初學者一路成長為中級開發者。
核心概念
1. watch 的定位
watch 用來觀察一個或多個響應式來源(data、props、computed),當來源的值改變時觸發回呼函式。它不同於 computed 的「依賴追蹤」——computed 只在需要時重新計算並回傳值,而 watch 執行副作用(side‑effects),如發送 AJAX、操作 DOM、或是更新其他狀態。
2. 基本語法(Options API)
export default {
data() {
return {
count: 0,
query: '',
};
},
watch: {
// 監聽單一屬性
count(newVal, oldVal) {
console.log(`count 從 ${oldVal} 變成 ${newVal}`);
},
// 監聽多個屬性(寫法 1:使用字串陣列)
// query: ['handler1', 'handler2'] // 這裡僅示意,可自行定義
// 監聽多個屬性(寫法 2:使用函式返回值)
fullInfo: {
handler(newVal, oldVal) {
console.log('fullInfo 改變', newVal);
},
deep: true, // 深度監聽(適用於物件/陣列)
immediate: true, // 初始化時立即執行一次
},
},
computed: {
fullInfo() {
return `${this.count} - ${this.query}`;
},
},
};
handler:必須接受newVal、oldVal兩個參數。deep:若監聽的目標是 物件或陣列,設定deep: true才能捕捉巢狀屬性的變化。immediate:在組件掛載後立即執行一次,常用於在初始化階段就需要根據資料執行副作用的情況。
3. 監聽多個來源
3.1 同時監聽多個屬性(陣列寫法)
watch: {
// 只要 count 或 query 任一變動,都會呼叫同一個 handler
['count', 'query'](newVals, oldVals) {
console.log('count 或 query 改變', newVals, oldVals);
},
}
3.2 監聽計算屬性
計算屬性本身不會直接觸發 watch,但因為它是基於其他響應式來源,當這些來源變更時,watch 仍會收到新值。
computed: {
fullInfo() {
return `${this.count} - ${this.query}`;
},
},
watch: {
fullInfo(newVal) {
console.log('fullInfo 變更為', newVal);
},
}
程式碼範例
以下提供 5 個實用範例,涵蓋常見需求與進階技巧。每段程式碼皆附上說明註解,方便直接套用於專案。
範例 1️⃣:表單輸入即時驗證
export default {
data() {
return {
email: '',
emailError: '',
};
},
watch: {
// 每次 email 改變即時驗證
email(newVal) {
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
this.emailError = emailPattern.test(newVal)
? '' // 合格則清除錯誤訊息
: '請輸入有效的 Email';
},
},
};
重點:
watch讓驗證邏輯與 UI 狀態分離,避免在template中寫過多的條件式。
範例 2️⃣:API 請求防抖(Debounce)
import { debounce } from 'lodash';
export default {
data() {
return {
searchKeyword: '',
results: [],
};
},
created() {
// 使用 lodash 的 debounce,避免使用者每次鍵入都發送請求
this.debouncedFetch = debounce(this.fetchResults, 500);
},
watch: {
searchKeyword() {
// 只要關鍵字變動,就觸發防抖函式
this.debouncedFetch();
},
},
methods: {
async fetchResults() {
if (!this.searchKeyword.trim()) {
this.results = [];
return;
}
try {
const res = await fetch(`/api/search?q=${encodeURIComponent(this.searchKeyword)}`);
this.results = await res.json();
} catch (e) {
console.error('搜尋失敗', e);
}
},
},
};
技巧:在
created或setup中先產生防抖函式,再於watch中直接呼叫,保持程式碼乾淨。
範例 3️⃣:深度監聽物件(表格編輯)
export default {
data() {
return {
tableData: [
{ id: 1, name: 'Alice', score: 85 },
{ id: 2, name: 'Bob', score: 92 },
],
};
},
watch: {
// 深度監聽 tableData 中任何欄位的變化
tableData: {
handler(newVal, oldVal) {
console.log('表格資料變更', newVal);
// 例如:自動儲存至後端
this.autoSave(newVal);
},
deep: true, // 必須設定才能捕捉巢狀屬性變化
immediate: false,
},
},
methods: {
async autoSave(data) {
// 假設有 save API
await fetch('/api/save', {
method: 'POST',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' },
});
},
},
};
注意:深度監聽會在每次巢狀屬性變動時觸發,若資料量龐大,建議搭配 節流(throttle)或 防抖 以減少不必要的呼叫。
範例 4️⃣:路由參數變化同步資料
import { useRoute, useRouter } from 'vue-router';
export default {
data() {
return {
page: 1,
items: [],
};
},
watch: {
// 當路由 query 中的 page 變動時,自動載入新頁面的資料
'$route.query.page': {
handler(newPage) {
this.page = Number(newPage) || 1;
this.fetchPage(this.page);
},
immediate: true, // 首次載入時即呼叫一次
},
},
methods: {
async fetchPage(page) {
const res = await fetch(`/api/items?page=${page}`);
this.items = await res.json();
},
},
};
說明:使用字串路徑(
'$route.query.page')直接監聽路由變化,讓 UI 與 URL 永遠保持同步。
範例 5️⃣:跨組件共享狀態(Vuex)
import { mapState } from 'vuex';
export default {
computed: {
...mapState(['authToken']),
},
watch: {
// 當全局的 authToken 變化時,更新本地的 API 客戶端
authToken: {
handler(newToken) {
// 假設有全局的 apiClient
this.$root.apiClient.setToken(newToken);
},
immediate: true,
},
},
};
重點:
watch能幫助我們在 全局狀態變動 時,立即調整本地或第三方套件的設定,保持應用一致性。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| 深度監聽導致效能問題 | deep: true 會在每次巢狀屬性變動時觸發,若資料結構龐大會頻繁呼叫 handler。 |
只對必要的屬性使用深度監聽,或在 handler 中加入 防抖 / 節流。 |
忘記 immediate |
有時需要在組件掛載時立即執行一次副作用(例如載入資料),若忘記設定會導致 UI 初始狀態錯誤。 | 根據需求加上 immediate: true,或在 mounted 中手動呼叫一次同樣的函式。 |
| 監聽錯誤的路徑 | 使用字串路徑(如 '$route.query.page')時,若拼寫錯誤或路由尚未初始化,watch 不會觸發。 |
確認路由已正確注入,或以 computed 包裝後再監聽。 |
在 watch 中直接改變被監聽的值 |
若在 watch 的 handler 內修改同一個被監聽的屬性,可能造成無限迴圈。 |
使用 非同步(如 nextTick)或在 handler 內判斷變化是否真的需要更新。 |
過度使用 watch |
有時可以使用 computed 直接取得衍生值,避免不必要的副作用。 | 先思考是否真的需要副作用(API 請求、DOM 操作),若僅是計算衍生值,改用 computed。 |
最佳實踐
- 只在需要副作用時使用
watch,衍生值使用computed。 - 將副作用抽離到
methods,保持watch內部簡潔。 - 使用
immediate配合nextTick,確保在 DOM 更新完成後執行。 - 對大型資料結構加防抖/節流,避免頻繁觸發 API。
- 統一錯誤處理:在
watch中的非同步操作,建議使用try/catch或全局錯誤攔截。
實際應用場景
即時搜尋(Autocomplete)
透過watch監聽使用者輸入,結合防抖與遠端 API,提供流暢的自動完成體驗。表單驗證與錯誤提示
當使用者修改欄位時即時檢查格式,將錯誤訊息寫入對應的錯誤屬性,讓 UI 自動顯示。與 URL 同步的分頁、過濾、排序
監聽$route.query中的分頁、過濾條件,變更時自動發送請求並更新畫面。全局認證狀態變更
當使用 Vuex 或 Pinia 管理的 token 改變時,利用watch更新 HTTP 客戶端的授權標頭。資料同步至本機儲存(LocalStorage / IndexedDB)
監聽重要的狀態,變更時即時寫入本機儲存,實現離線支援或狀態持久化。
總結
watch 是 Vue3 Options API 中不可或缺的 副作用監聽器。透過正確的 語法(handler、deep、immediate)與 實務技巧(防抖、節流、錯誤處理),我們可以:
- 即時回應資料變化,如表單驗證、搜尋建議。
- 同步 UI 與外部資源,包括 API 請求、路由參數、全局狀態。
- 保持程式碼可讀與可維護,將副作用抽離至
methods,避免在template中寫雜湊邏輯。
在開發過程中,請謹記只在需要副作用時使用 watch,其餘情況盡量使用 computed 取得衍生值。合理運用 watch,不僅能提升開發效率,也能讓你的 Vue3 應用在效能與使用者體驗上更上一層樓。祝你玩得開心,寫出更乾淨、更強大的 Vue3 程式!