Vue 中 watch(監聽器)的使用方法
在 Vue 中,watch(監聽器)就像一個 “數據哨兵”,專門監聽響應式數據的變化。當被監聽的數據發生改變時,watch 會自動觸發預設的回調函數,我們可以在回調中執行自定義邏輯(比如發送請求、更新 DOM、執行計算等),是處理數據變化後副作用的核心工具。
一、基礎用法:監聽單個數據
最常用的場景是監聽單個響應式數據(ref 或 reactive),當數據變化時執行回調。
1. 監聽 ref 數據
<template>
<div class="watch-demo">
<h3>監聽ref數據</h3>
<input v-model="username" placeholder="輸入用户名">
<p>用户名變化日誌:{{ log }}</p>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
// 被監聽的響應式數據
const username = ref('')
const log = ref('')
// 基礎監聽:監聽username的變化
watch(username, (newVal, oldVal) => {
// newVal:變化後的值;oldVal:變化前的值
log.value = `用户名從"${oldVal}"變為"${newVal}"`
})
</script>
這裏 watch 監聽username的變化,每次輸入框內容修改時,回調函數會接收新值(newVal)和舊值(oldVal),並更新日誌信息。
2. 監聽 reactive 對象的單個屬性
如果需要監聽 reactive 對象中的某個屬性,需通過 “字符串路徑” 或 “函數返回值” 的方式指定:
<template>
<div>
<h3>監聽reactive對象屬性</h3>
<input v-model="user.age" type="number" placeholder="輸入年齡">
<p>年齡變化提示:{{ tip }}</p>
</div>
</template>
<script setup>
import { reactive, watch } from 'vue'
// reactive對象
const user = reactive({
name: '張三',
age: 25
})
const tip = ref('')
// 方式1:通過字符串路徑監聽單個屬性
watch('user.age', (newAge, oldAge) => {
tip.value = `年齡從${oldAge}歲變為${newAge}歲`
})
// 方式2:通過函數返回值監聽(更推薦,支持複雜路徑)
// watch(() => user.age, (newAge, oldAge) => {
// tip.value = `年齡從${oldAge}歲變為${newAge}歲`
// })
</script>
二、進階用法:監聽多個數據、深度監聽
1. 監聽多個數據
可以通過數組形式同時監聽多個響應式數據,任意一個數據變化都會觸發回調:
<template>
<div>
<h3>監聽多個數據</h3>
<input v-model="a" type="number" placeholder="輸入數字a">
<input v-model="b" type="number" placeholder="輸入數字b">
<p>總和:{{ sum }}</p>
</div>
</template>
<script setup>
import { ref, watch, computed } from 'vue'
const a = ref(0)
const b = ref(0)
const sum = ref(0)
// 監聽a和b的變化,任意一個變化都觸發
watch([a, () => b.value], ([newA, newB], [oldA, oldB]) => {
sum.value = newA + newB
console.log(`a從${oldA}變${newA},b從${oldB}變${newB}`)
})
</script>
回調函數的第一個參數是 “新值數組”,第二個參數是 “舊值數組”,順序與監聽的數組一致。
2. 深度監聽(監聽對象 / 數組內部變化)
默認情況下,watch 只監聽數據的 “引用變化”(比如給對象重新賦值、給數組重新賦值),不會監聽對象內部屬性或數組元素的變化。此時需要開啓deep: true實現深度監聽。
監聽 reactive 對象內部變化
<template>
<div>
<h3>深度監聽對象</h3>
<input v-model="user.name" placeholder="修改姓名">
<input v-model="user.age" type="number" placeholder="修改年齡">
<p>用户信息變化:{{ userLog }}</p>
</div>
</template>
<script setup>
import { reactive, watch } from 'vue'
const user = reactive({
name: '張三',
age: 25
})
const userLog = ref('')
// 深度監聽user對象(開啓deep: true)
watch(
user,
(newUser, oldUser) => {
userLog.value = `姓名:${oldUser.name}→${newUser.name},年齡:${oldUser.age}→${newUser.age}`
},
{ deep: true } // 開啓深度監聽
)
</script>
開啓deep: true後,無論是修改user.name還是user.age,都會觸發 watch 回調。
監聽數組元素變化
<template>
<div>
<h3>深度監聽數組</h3>
<button @click="addItem">添加商品</button>
<button @click="updateFirstItem">修改第一個商品</button>
<ul>
<li v-for="(item, index) in goods" :key="index">{{ item }}</li>
</ul>
<p>數組變化日誌:{{ goodsLog }}</p>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
const goods = ref(['Vue教程', 'JS進階'])
const goodsLog = ref('')
// 深度監聽數組(數組元素變化時觸發)
watch(
goods,
(newGoods, oldGoods) => {
goodsLog.value = `數組從[${oldGoods}]變為[${newGoods}]`
},
{ deep: true }
)
// 添加數組元素
const addItem = () => {
goods.value.push(`新商品${goods.value.length + 1}`)
}
// 修改數組元素
const updateFirstItem = () => {
goods.value[0] = 'Vue 3實戰教程'
}
</script>
數組的push、splice、修改元素等操作,都會被深度監聽捕獲。
3. 立即執行(immediate: true)
默認情況下,watch 回調只在數據變化時觸發。如果需要 “初始化時立即執行一次”(比如頁面加載時就根據初始值發送請求),可以開啓immediate: true:
<template>
<div>
<h3>立即執行的watch</h3>
<p>當前搜索關鍵詞:{{ keyword }}</p>
<p>搜索結果:{{ result }}</p>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
const keyword = ref('Vue')
const result = ref('')
// 開啓immediate:初始化時執行一次,之後關鍵詞變化時再執行
watch(
keyword,
async (newVal) => {
// 模擬發送搜索請求
result.value = `正在搜索"${newVal}"...`
const mockRes = await new Promise((resolve) => {
setTimeout(() => resolve(`找到${newVal}相關結果100條`), 500)
})
result.value = mockRes
},
{ immediate: true }
)
</script>
頁面加載時,watch 會立即執行一次回調,根據初始關鍵詞 “Vue” 發送搜索請求,之後修改keyword時也會觸發搜索。
三、特殊用法:監聽對象的特定屬性(精準監聽)
如果只需要監聽對象的某個特定屬性(而非整個對象),可以通過 “函數返回值” 的方式精準監聽,無需開啓深度監聽(性能更優):
<template>
<div>
<h3>精準監聽對象屬性</h3>
<input v-model="user.address.city" placeholder="修改城市">
<p>城市變化:{{ cityLog }}</p>
</div>
</template>
<script setup>
import { reactive, watch } from 'vue'
const user = reactive({
name: '張三',
address: {
city: '北京',
street: 'XX路'
}
})
const cityLog = ref('')
// 精準監聽user.address.city,無需deep
watch(
() => user.address.city, // 函數返回要監聽的屬性
(newCity, oldCity) => {
cityLog.value = `城市從${oldCity}變為${newCity}`
}
)
</script>
這種方式只會監聽user.address.city的變化,修改user.name或user.address.street都不會觸發回調,性能比深度監聽更優。
四、watch 與 computed 的區別(關鍵!)
很多時候會混淆 watch 和 computed,核心區別如下:
| 特性 | watch | computed |
|---|---|---|
| 核心用途 | 處理數據變化後的副作用(發請求、改 DOM、定時器等) | 處理數據的衍生邏輯(數據計算、格式化、篩選) |
| 觸發時機 | 數據變化時觸發(可配置立即執行) | 依賴數據變化時自動重新計算 |
| 返回值 | 無返回值,靠回調執行邏輯 | 有返回值,可直接在模板中使用 |
| 緩存機制 | 無緩存,數據變化必觸發回調 | 有緩存,依賴不變時直接返回緩存結果 |
| 適用場景 | 數據變化後需要執行異步操作、複雜邏輯 | 簡單的衍生數據計算、模板中複雜表達式 |
示例對比:
-
用 computed:根據用户名和密碼判斷是否可提交(衍生數據);
-
用 watch:監聽表單提交狀態,變化時發送登錄請求(副作用)。
五、注意事項
-
避免監聽複雜對象:直接監聽大型對象或數組(開啓 deep)會影響性能,儘量採用 “精準監聽” 特定屬性;
-
清理副作用:如果 watch 回調中創建了定時器、事件監聽等,需要在組件卸載時清理,避免內存泄漏(可通過 watch 的返回函數實現):
watch(username, (newVal) => {
const timer = setTimeout(() => {
console.log('用户名變化:', newVal)
}, 1000)
// 返回清理函數,組件卸載時執行
return () => clearTimeout(timer)
})
- 區分 ref 和 reactive 的監聽差異:
-
- 監聽 ref 數據:直接傳入 ref 對象(如watch(username, ...));
-
- 監聽 reactive 對象屬性:需用函數返回(如watch(() => user.age, ...));
- 舊值的侷限性:監聽數組或對象時,oldVal 和 newVal 可能指向同一個引用(因為 Vue 不會深拷貝),如果需要對比舊值和新值,需手動深拷貝(如用JSON.parse(JSON.stringify(oldVal)))。
總結
watch 是 Vue 中處理數據變化副作用的核心工具,支持單個數據、多個數據、對象屬性、數組等多種監聽場景,還能通過deep、immediate等配置滿足複雜需求。它與 computed 分工明確:computed 負責 “數據衍生”,watch 負責 “副作用處理”。掌握 watch 的基礎用法、精準監聽、深度監聽和清理機制,能讓我們在數據變化時靈活控制邏輯,應對各類異步操作、DOM 更新等場景,讓代碼更健壯、更易維護。