➤ 簡單來說是一種「主動監聽資料變化並執行副作用」的機制
當你使用 watch(source, callback)
,你就是告訴 Vue:「嘿,如果這個資料(ref、reactive、computed 等)變了,幫我做點事。」這個「做點事」通常是像:呼叫 API、寫入 localStorage、跳出通知、同步外部資料等副作用操作。
和 computed 不同,watch 並不會回傳一個值,而是觸發一段行為。它也不會快取,因為它不是用來算資料的,而是用來回應變化的。
這種「資料變 → 我來做某事」的設計,就是 watch 的本質。
可以把 watch 想像成 Vue 世界裡的「告警系統」,你幫它設定「什麼條件下要叫」,資料一動,它就會「叮~」一聲提醒你,然後你再決定要不要採取行動。
- 具備「副作用執行」能力的響應式監聽器
- 當監聽的資料變動時,立即執行 callback 函式,用來處理非計算邏輯的行為
- 實作上會建立一個 effect,並追蹤依賴來源,一旦變化就觸發副作用
- 支援多種來源(ref、reactive、getter 函式、陣列組合),也支援選項如 immediate、deep、flush 等以細緻控制執行時機與方式
<script setup>
import { ref, watch } from "vue";
const count = ref(0);
const message = ref("");
// 監聽 count 變化
watch(
count,
(newValue, oldValue) => {
if (newValue > 10) {
message.value = "計數超過 10";
} else {
message.value = "計數在 10 以下";
}
},
{
immediate: true, // 初次載入時也執行一次
}
);
</script>
<template>
<div>
<p>當前計數:{{ count }}</p>
<button class="btn" @click="count++">增加計數</button>
<button class="btn" @click="count--">減少計數</button>
<p>{{ message }}</p>
</div>
</template>
watch()
的第一個參數是「追蹤目標(source)」,可以是:
- 一個 getter 函式
() => something
- 一個 ref 物件
- 一個 reactive 物件(但這樣會監聽整個物件的任意屬性變化)
<script setup>
const x = ref(0)
const y = ref(0)
const obj = reactive({ count: 0 })
// ref
watch(x, (newX) => {
console.log(`x is ${newX}`)
})
// getter 函式
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
// 多個來源組成陣列
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
// 使用 reactive 若要監聽屬性,必須使用 getter 函式
watch(
// ❗ 若直接傳入 obj.count,等同於傳入一個 primitive 值(如 number)
// 這樣 Vue 在初始化 watch 時就會直接取值,無法建立依賴關係
() => obj.count,
(count) => {
console.log(`Count is: ${count}`)
}
)
</script>
watch 可以加上一些選項來控制監聽的行為,主要有三個常見的選項
watch 預設是僅當數據源變化時,才會執行回調。通過設定 immediate: true
,會在 watch 一被建立就立即執行一次回呼函式。
<script>
watch(
source,
// 立即執行,且當 `source` 改變時再次執行
(newValue, oldValue) => {...},
{ immediate: true }
)
</script>
當監聽的是物件或陣列時,設為 deep: true
可以深層監聽內部變化(例如物件屬性變動)。
<script>
watch(
source,
(newValue, oldValue) => {...},
{ deep: true }
)
</script>
透過 flush
選項控制回呼函式的執行時機:
'pre'
:DOM 更新前(預設)'post'
:DOM 更新後'sync'
:同步執行(會在資料變化當下立即執行)
<script>
watch(
source,
(newValue, oldValue) => {...},
{ flush: 'post' }
)
</script>
在 Vue 3.5+ 中,deep 選項也可以是一個數字,表示最大遍歷深度 - 即 Vue 應該遍歷物件的嵌套屬性的層級
補充
- 使用
deep
會影響效能,請謹慎使用,尤其是對大型物件 immediate
適合在需要初次取得資料的場景flush: 'post'
適合需要讀取 DOM 或等待畫面更新完畢再執行的操作
watchEffect 是一種自動收集依賴的副作用函式(reactive effect)。它會「自動偵測你裡面用到的響應式資料」,然後當那些資料變動時,重新執行你寫的程式。
- 不用明確告訴它要監聽誰
- 它會看你用到什麼 ref 或 reactive 資料,並幫你追蹤
- 這讓你可以很快速地「響應某些資料變化並做點事」
與 watch 主要區別是追蹤響應式依賴的方式:
- 只監聽明確定義參數的數據源
- 它不會追蹤任何在回調中的東西
- 僅在數據源確實改變時才會觸發回調
- watch 會避免在發生副作用時追蹤依賴,因此,我們能更加精確地控制回調函式的觸發時機
- 會在同步執行過程中,自動追蹤所有能訪問到的響應式屬性
- 這更方便且程式碼往往更簡潔,但有時其響應性依賴關係會不那麼明確
<script>
const a = ref(1)
const b = ref(2)
const total = ref(0)
// 使用 watch 指定監聽 a 和 b
watch([a, b], ([newA, newB]) => {
total.value = newA + newB
})
a.value = 3 // 會觸發,計算 total = 5
b.value = 4 // 會觸發,計算 total = 7
</script>
<script>
const a = ref(1)
const b = ref(2)
const total = ref(0)
// 使用 watchEffect 自動追蹤 a 和 b
watchEffect(() => {
total.value = a.value + b.value
})
a.value = 3 // 會觸發,計算 total = 5
b.value = 4 // 會觸發,計算 total = 7
</script>
- 根據資料變化,自動更新畫面邏輯
- 自動觸發某些副作用(例如打 API、console log、改變其他資料)
- 在
setup()
裡取代 onMounted
+ watch
的一些用法
補充
watchEffect 僅會在其 同步執行 期間,才追蹤依賴。在使用非同步回調時,只有在第一個 await
正常工作前訪問到的屬性才會被追蹤
使用場景 | 建議用 |
---|
監聽一個或多個明確變數(精準控制) | watch |
根據「使用到的 ref 或 reactive」自動響應 | watchEffect |
初始時跑一次 API 並監聽參數變化時重跑 | watch + immediate: true |
在元件建立時執行 reactive 資料推導或副作用邏輯 | watchEffect |