Vue3中watch與watchEffect使用方法詳解
一、監(jiān)聽(tīng)器的基本概念
在Vue3中,watch和watchEffect都是用于監(jiān)聽(tīng)數(shù)據(jù)變化并執(zhí)行副作用的API,但它們的使用方式和適用場(chǎng)景有所不同。
- watch:需要顯式指定監(jiān)聽(tīng)的數(shù)據(jù)源,惰性執(zhí)行(默認(rèn)數(shù)據(jù)變化時(shí)才執(zhí)行),可以獲取新舊值
- watchEffect:自動(dòng)收集依賴(lài),立即執(zhí)行,無(wú)法獲取舊值
二、watch的使用方法
2.1 基礎(chǔ)用法:監(jiān)聽(tīng)單個(gè)ref數(shù)據(jù)
<template>
<div>
<p>計(jì)數(shù)器: {{ count }}</p>
<button @click="count++">增加</button>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
// 創(chuàng)建響應(yīng)式數(shù)據(jù)
const count = ref(0)
// 監(jiān)聽(tīng)count變化
// 第一個(gè)參數(shù):要監(jiān)聽(tīng)的數(shù)據(jù)源
// 第二個(gè)參數(shù):回調(diào)函數(shù),接收新值和舊值
watch(count, (newValue, oldValue) => {
console.log(`count從${oldValue}變?yōu)?{newValue}`)
})
</script>
2.2 監(jiān)聽(tīng)多個(gè)數(shù)據(jù)源【監(jiān)聽(tīng)多個(gè)數(shù)據(jù),用數(shù)組形式傳入】
import { ref, watch } from 'vue'
const firstName = ref('張')
const lastName = ref('三')
// 監(jiān)聽(tīng)多個(gè)數(shù)據(jù),用數(shù)組形式傳入
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
console.log(`姓名從${oldFirst}${oldLast}變?yōu)?{newFirst}${newLast}`)
})
2.3 監(jiān)聽(tīng)對(duì)象屬性【監(jiān)聽(tīng)對(duì)象的某個(gè)屬性,使用getter函數(shù)】
import { reactive, watch } from 'vue'
const user = reactive({
name: '張三',
age: 20
})
// 監(jiān)聽(tīng)對(duì)象的某個(gè)屬性,使用getter函數(shù)
watch(
() => user.age, // getter函數(shù),返回要監(jiān)聽(tīng)的屬性
(newAge, oldAge) => { console.log(`年齡從${oldAge}變?yōu)?{newAge}`)
}
)
2.4 深度監(jiān)聽(tīng)
當(dāng)監(jiān)聽(tīng)對(duì)象或數(shù)組時(shí),默認(rèn)情況下watch不會(huì)監(jiān)聽(tīng)內(nèi)部屬性變化,需要使用deep: true開(kāi)啟深度監(jiān)聽(tīng):
import { ref, watch } from 'vue'
const user = ref({
name: '張三',
address: {
city: '北京'
}
})
// 深度監(jiān)聽(tīng)對(duì)象內(nèi)部變化
watch(
user,
(newVal, oldVal) => { console.log('用戶(hù)信息變化:', newVal)},
{ deep: true } // 開(kāi)啟深度監(jiān)聽(tīng)
)
// 修改深層屬性會(huì)觸發(fā)監(jiān)聽(tīng)
user.value.address.city = '上海'
2.5 立即執(zhí)行
默認(rèn)情況下,watch是惰性的,只有數(shù)據(jù)變化時(shí)才執(zhí)行。使用immediate: true可以讓它在初始化時(shí)立即執(zhí)行:
watch(
count,
(newValue, oldValue) => {
console.log(`count變化: ${newValue}`)
},
{ immediate: true } // 立即執(zhí)行
)
三、watchEffect的使用方法【可以不用指定監(jiān)聽(tīng)的數(shù)據(jù)】
3.1 基礎(chǔ)用法
watchEffect會(huì)自動(dòng)收集函數(shù)內(nèi)使用的響應(yīng)式數(shù)據(jù)作為依賴(lài):
自動(dòng)追蹤所有響應(yīng)式依賴(lài), 每次依賴(lài)變更都會(huì)執(zhí)行此函數(shù)
<template>
<div>
<p>計(jì)數(shù)器: {{ count }}</p>
<button @click="count++">增加</button>
</div>
</template>
<script setup>
import { ref, watchEffect } from 'vue'
const count = ref(0)
// watchEffect會(huì)自動(dòng)收集依賴(lài)
// 1. 初始化時(shí)立即執(zhí)行一次
// 2. 當(dāng)函數(shù)內(nèi)依賴(lài)的響應(yīng)式數(shù)據(jù)變化時(shí)重新執(zhí)行
watchEffect( () => {console.log(`count的值是: ${count.value}`)}
)
</script>
3.2 停止監(jiān)聽(tīng)
watchEffect返回一個(gè)停止函數(shù),調(diào)用它可以停止監(jiān)聽(tīng):
import { ref, watchEffect } from 'vue'
const count = ref(0)
// 獲取停止函數(shù)
const stop = watchEffect(() => {
console.log(`count: ${count.value}`)
})
// 5秒后停止監(jiān)聽(tīng)
setTimeout(() => {
stop()
console.log('已停止監(jiān)聽(tīng)')
}, 5000)
3.3 清理副作用
watchEffect的回調(diào)函數(shù)可以返回一個(gè)清理函數(shù),在下次執(zhí)行前或停止監(jiān)聽(tīng)時(shí)調(diào)用:
watchEffect((onInvalidate) => {
// 模擬異步操作
const timer = setTimeout(() => {
console.log('異步操作完成')
}, 1000)
// 清理函數(shù),在下次執(zhí)行前或停止監(jiān)聽(tīng)時(shí)調(diào)用
onInvalidate(() => {
clearTimeout(timer)
console.log('清理定時(shí)器')
})
})
四、watch與watchEffect的區(qū)別
| 特性 | watch | watchEffect |
|---|---|---|
| 依賴(lài)收集 | 需要顯式指定監(jiān)聽(tīng)源 | 自動(dòng)收集函數(shù)內(nèi)的響應(yīng)式依賴(lài) |
| 執(zhí)行時(shí)機(jī) | 默認(rèn)惰性執(zhí)行(數(shù)據(jù)變化時(shí)) | 立即執(zhí)行,然后響應(yīng)式追蹤 |
| 新舊值 | 可以獲取新值和舊值 | 只能獲取新值 |
| 適用場(chǎng)景 | 需要知道數(shù)據(jù)變化前后的值 | 只需響應(yīng)數(shù)據(jù)變化,不需要舊值 |
| 控制粒度 | 精確控制監(jiān)聽(tīng)源 | 自動(dòng)追蹤所有依賴(lài) |
五、高級(jí)用法
5.1 監(jiān)聽(tīng)執(zhí)行時(shí)機(jī)控制
使用flush選項(xiàng)控制回調(diào)執(zhí)行時(shí)機(jī):
watch(
count,
() => {
// DOM更新后執(zhí)行
},
{ flush: 'post' } // 'pre'(默認(rèn)) | 'post' | 'sync'
)
5.2 調(diào)試監(jiān)聽(tīng)器
使用onTrack和onTrigger選項(xiàng)調(diào)試依賴(lài):
watch(
count,
() => {
console.log('count變化了')
},
{
onTrack(e) {
console.log('依賴(lài)被追蹤:', e)
},
onTrigger(e) {
console.log('監(jiān)聽(tīng)器被觸發(fā):', e)
}
}
)
5.3 暫停與恢復(fù)監(jiān)聽(tīng)(Vue3.5+)
Vue3.5+版本新增了暫停和恢復(fù)監(jiān)聽(tīng)的功能:
const { pause, resume } = watch(count, () => {
console.log('count變化了')
})
// 暫停監(jiān)聽(tīng)
pause()
// 恢復(fù)監(jiān)聽(tīng)
resume()
六、實(shí)戰(zhàn)示例
6.1 表單驗(yàn)證(使用watch)
import { ref, watch } from 'vue'
const username = ref('')
const errorMessage = ref('')
// 監(jiān)聽(tīng)用戶(hù)名變化進(jìn)行驗(yàn)證
watch(
username,
(newVal) => {
if (newVal.length < 3) {
errorMessage.value = '用戶(hù)名至少3個(gè)字符'
} else {
errorMessage.value = ''
}
},
{ immediate: true } // 初始化時(shí)驗(yàn)證
)
6.2 數(shù)據(jù)加載與清理(使用watchEffect)
import { ref, watchEffect } from 'vue'
const userId = ref(1)
const userData = ref(null)
watchEffect(async (onInvalidate) => {
// 加載數(shù)據(jù)
const controller = new AbortController()
const signal = controller.signal
try {
const response = await fetch(`/api/user/${userId.value}`, { signal })
userData.value = await response.json()
} catch (error) {
if (error.name !== 'AbortError') {
console.error('加載失敗:', error)
}
}
// 清理函數(shù),取消上一次請(qǐng)求
onInvalidate(() => {
controller.abort()
})
})
七、總結(jié)
- watch適合需要精細(xì)控制監(jiān)聽(tīng)源、需要新舊值對(duì)比或需要延遲執(zhí)行的場(chǎng)景
- watchEffect適合簡(jiǎn)單的副作用處理,自動(dòng)追蹤依賴(lài),代碼更簡(jiǎn)潔
- 優(yōu)先使用watchEffect處理簡(jiǎn)單邏輯,使用watch處理復(fù)雜場(chǎng)景
- 注意清理副作用,避免內(nèi)存泄漏
- 組件卸載時(shí),同步創(chuàng)建的監(jiān)聽(tīng)器會(huì)自動(dòng)停止,異步創(chuàng)建的需要手動(dòng)停止## 八、深入理解與高級(jí)技巧
7.1 watch監(jiān)聽(tīng)reactive對(duì)象的注意事項(xiàng)
當(dāng)監(jiān)聽(tīng)reactive創(chuàng)建的響應(yīng)式對(duì)象時(shí),有一些特殊情況需要注意:
import { reactive, watch } from 'vue'
const user = reactive({
name: '張三',
age: 20
})
// 情況一:直接監(jiān)聽(tīng)reactive對(duì)象
// 此時(shí)會(huì)自動(dòng)開(kāi)啟深度監(jiān)聽(tīng),無(wú)需設(shè)置deep: true
watch(user, (newVal, oldVal) => {
console.log('用戶(hù)信息變化', newVal)
})
// 情況二:監(jiān)聽(tīng)reactive對(duì)象的屬性
// 必須使用getter函數(shù),否則無(wú)法觸發(fā)監(jiān)聽(tīng)
watch(
() => user.age, // 正確寫(xiě)法
(newAge) => {
console.log('年齡變化:', newAge)
}
)
// 錯(cuò)誤寫(xiě)法:直接監(jiān)聽(tīng)屬性無(wú)法觸發(fā)
watch(user.age, () => {
console.log('年齡變化') // 不會(huì)執(zhí)行
})
7.2 watchEffect的依賴(lài)追蹤細(xì)節(jié)
watchEffect的依賴(lài)追蹤是動(dòng)態(tài)的,只追蹤函數(shù)執(zhí)行過(guò)程中實(shí)際訪(fǎng)問(wèn)的響應(yīng)式數(shù)據(jù):
import { ref, watchEffect } from 'vue'
const a = ref(0)
const b = ref(0)
watchEffect(() => {
console.log('watchEffect執(zhí)行')
if (a.value > 5) {
console.log('b的值:', b.value)
}
})
// 初始執(zhí)行: 輸出 "watchEffect執(zhí)行"
a.value = 3 // 執(zhí)行: 輸出 "watchEffect執(zhí)行" (a被訪(fǎng)問(wèn))
b.value = 5 // 不執(zhí)行 (b未被訪(fǎng)問(wèn))
a.value = 6 // 執(zhí)行: 輸出 "watchEffect執(zhí)行" 和 "b的值: 5" (a和b都被訪(fǎng)問(wèn))
b.value = 10 // 執(zhí)行: 輸出 "watchEffect執(zhí)行" 和 "b的值: 10" (b現(xiàn)在被訪(fǎng)問(wèn)了)
7.3 高級(jí)調(diào)試技巧
使用onTrack和onTrigger選項(xiàng)可以幫助我們調(diào)試監(jiān)聽(tīng)器的行為:
watch(
count,
() => {
console.log('count變化了')
},
{
onTrack(e) {
// 當(dāng)依賴(lài)被追蹤時(shí)觸發(fā)
console.log(`追蹤到依賴(lài): ${e.target}的${e.key}屬性`)
},
onTrigger(e) {
// 當(dāng)監(jiān)聽(tīng)器被觸發(fā)時(shí)觸發(fā)
console.log(`監(jiān)聽(tīng)器觸發(fā)原因: ${e.type}`)
}
}
)
7.4 性能優(yōu)化策略
7.4.1 避免不必要的深度監(jiān)聽(tīng)
深度監(jiān)聽(tīng)會(huì)遞歸遍歷對(duì)象的所有屬性,對(duì)于大型對(duì)象會(huì)影響性能:
// 不推薦:深度監(jiān)聽(tīng)大型對(duì)象
watch(largeObject, () => {
// ...
}, { deep: true })
// 推薦:只監(jiān)聽(tīng)需要的屬性
watch(
() => largeObject.importantProperty,
() => {
// ...
}
)
7.4.2 使用防抖/節(jié)流優(yōu)化頻繁觸發(fā)
對(duì)于輸入框等頻繁變化的場(chǎng)景,可以結(jié)合防抖/節(jié)流:
import { ref, watchEffect } from 'vue'
import { debounce } from 'lodash'
const searchInput = ref('')
// 使用防抖優(yōu)化搜索請(qǐng)求
const debouncedSearch = debounce(async (value) => {
const results = await fetch(`/api/search?q=${value}`)
// 處理結(jié)果
}, 300)
watchEffect(() => {
debouncedSearch(searchInput.value)
})
八、常見(jiàn)問(wèn)題與解決方案
8.1 監(jiān)聽(tīng)器不觸發(fā)的常見(jiàn)原因
- 監(jiān)聽(tīng)了非響應(yīng)式數(shù)據(jù)
// 錯(cuò)誤:監(jiān)聽(tīng)普通變量
let count = 0
watch(count, () => { /* 不會(huì)觸發(fā) */ })
// 正確:監(jiān)聽(tīng)響應(yīng)式數(shù)據(jù)
const count = ref(0)
watch(count, () => { /* 會(huì)觸發(fā) */ })
- 直接修改數(shù)組索引或長(zhǎng)度
const list = ref([1, 2, 3]) // 錯(cuò)誤:直接修改索引 list.value[0] = 100 // 不會(huì)觸發(fā)監(jiān)聽(tīng) // 正確:使用數(shù)組方法或set list.value.push(4) // 會(huì)觸發(fā) list.value.splice(0, 1, 100) // 會(huì)觸發(fā)
- 監(jiān)聽(tīng)對(duì)象新增屬性
const user = reactive({ name: '張三' })
// 錯(cuò)誤:直接添加屬性
user.age = 20 // 默認(rèn)不會(huì)觸發(fā)監(jiān)聽(tīng)
// 正確:使用Vue.set或重新賦值
user.age = 20 // 在Vue3中,reactive對(duì)象新增屬性會(huì)觸發(fā)監(jiān)聽(tīng)
// 或?qū)τ趓ef對(duì)象
user.value = { ...user.value, age: 20 }
8.2 watch與computed的選擇
| 場(chǎng)景 | 推薦使用 | 原因 |
|---|---|---|
| 數(shù)據(jù)轉(zhuǎn)換/計(jì)算 | computed | 緩存結(jié)果,更高效 |
| 數(shù)據(jù)變化時(shí)執(zhí)行異步操作 | watch | 適合處理副作用 |
| 需要知道數(shù)據(jù)變化前后的值 | watch | computed無(wú)法獲取舊值 |
| 簡(jiǎn)單的依賴(lài)追蹤 | watchEffect | 代碼更簡(jiǎn)潔 |
九、完整實(shí)戰(zhàn)案例:搜索功能實(shí)現(xiàn)
下面是一個(gè)結(jié)合watch、watchEffect和computed的完整搜索功能實(shí)現(xiàn):
<template>
<div class="search-container">
<input
v-model="searchQuery"
placeholder="搜索..."
@input="handleInput"
>
<div v-if="isSearching" class="loading">搜索中...</div>
<div v-if="errorMessage" class="error">{{ errorMessage }}</div>
<ul v-else-if="results.length">
<li v-for="result in results" :key="result.id">{{ result.name }}</li>
</ul>
<div v-else-if="!isSearching && searchQuery">無(wú)結(jié)果</div>
</div>
</template>
<script setup>
import { ref, watch, watchEffect, computed, onUnmounted } from 'vue'
// 響應(yīng)式數(shù)據(jù)
const searchQuery = ref('')
const results = ref([])
const isSearching = ref(false)
const errorMessage = ref('')
const debouncedQuery = ref('')
// 防抖處理
let timeoutId = null
const handleInput = () => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => {
debouncedQuery.value = searchQuery.value
}, 300)
}
// 使用watch處理搜索邏輯
watch(
debouncedQuery,
async (newQuery) => {
if (!newQuery.trim()) {
results.value = []
return
}
isSearching.value = true
errorMessage.value = ''
try {
const response = await fetch(`/api/search?q=${encodeURIComponent(newQuery)}`)
if (!response.ok) throw new Error('搜索失敗')
results.value = await response.json()
} catch (err) {
errorMessage.value = err.message
} finally {
isSearching.value = false
}
},
{ immediate: false }
)
// 使用watchEffect清理定時(shí)器
watchEffect((onInvalidate) => {
// 組件卸載時(shí)清理定時(shí)器
onInvalidate(() => {
clearTimeout(timeoutId)
})
})
// 清理函數(shù)
onUnmounted(() => {
clearTimeout(timeoutId)
})
</script>
十、總結(jié)與最佳實(shí)踐
- 優(yōu)先使用watchEffect:當(dāng)你需要自動(dòng)追蹤多個(gè)依賴(lài),且不需要舊值時(shí)
- 使用watch的場(chǎng)景:需要明確監(jiān)聽(tīng)源、需要舊值、需要延遲執(zhí)行或深度監(jiān)聽(tīng)
- 性能考量:
- 避免對(duì)大型對(duì)象使用深度監(jiān)聽(tīng)
- 對(duì)頻繁變化的數(shù)據(jù)使用防抖/節(jié)流
- 及時(shí)清理副作用,避免內(nèi)存泄漏
- 調(diào)試技巧:使用onTrack和onTrigger追蹤依賴(lài)問(wèn)題
- 組件卸載:異步創(chuàng)建的監(jiān)聽(tīng)器需要手動(dòng)停止
通過(guò)合理選擇watch和watchEffect,并遵循最佳實(shí)踐,可以寫(xiě)出更高效、更可維護(hù)的Vue3代碼。理解它們的內(nèi)部工作原理和適用場(chǎng)景,將幫助你在不同的業(yè)務(wù)需求中做出正確的技術(shù)選擇。## 十二、底層原理與實(shí)現(xiàn)機(jī)制
10.1 響應(yīng)式依賴(lài)收集流程
Vue3的監(jiān)聽(tīng)器基于響應(yīng)式系統(tǒng)工作,其核心流程如下:
1. 當(dāng)watch/watchEffect執(zhí)行時(shí),會(huì)創(chuàng)建一個(gè)"副作用函數(shù)"(effect)
2. 執(zhí)行副作用函數(shù),期間訪(fǎng)問(wèn)的響應(yīng)式數(shù)據(jù)會(huì)被"追蹤"(track)
3. 響應(yīng)式數(shù)據(jù)變化時(shí),會(huì)"觸發(fā)"(trigger)相關(guān)的副作用函數(shù)重新執(zhí)行
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 響應(yīng)式數(shù)據(jù) │────?│ 依賴(lài)追蹤器 │────?│ 副作用函數(shù)隊(duì)列 │
└─────────────┘ └─────────────┘ └─────────────┘
▲ │
│ ▼
└──────────────────────────────────────────┘
(數(shù)據(jù)變化時(shí)觸發(fā)副作用執(zhí)行)
10.2 watch與watchEffect的內(nèi)部差異
// watch的簡(jiǎn)化實(shí)現(xiàn)
function watch(source, callback, options) {
// 創(chuàng)建getter函數(shù)
const getter = isRef(source)
? () => source.value
: typeof source === 'function'
? source
: () => traverse(source);
// 執(zhí)行副作用
let oldValue = undefined;
const effect = effectFn(() => getter(), {
lazy: true, // 懶執(zhí)行,首次不觸發(fā)
scheduler: () => { // 調(diào)度函數(shù)
const newValue = getter();
callback(newValue, oldValue);
oldValue = newValue;
}
});
// 初始化
if (options.immediate) {
callback(getter(), oldValue);
} else {
oldValue = effect();
}
return () => stop(effect);
}
// watchEffect的簡(jiǎn)化實(shí)現(xiàn)
function watchEffect(effect, options) {
const _effect = effectFn(effect, {
lazy: false, // 立即執(zhí)行
scheduler: queueJob, // 默認(rèn)調(diào)度器
...options
});
return () => stop(_effect);
}
十一、API參數(shù)全解析
11.1 watch完整參數(shù)說(shuō)明
// TypeScript類(lèi)型定義
function watch<T>(
source: WatchSource<T> | WatchSource<T>[],
callback: WatchCallback<T>,
options?: WatchOptions
): StopHandle
interface WatchOptions extends WatchEffectOptions {
immediate?: boolean // 是否立即執(zhí)行,默認(rèn)false
deep?: boolean // 是否深度監(jiān)聽(tīng),默認(rèn)false
flush?: 'pre' | 'post' | 'sync' // 執(zhí)行時(shí)機(jī),默認(rèn)'pre'
once?: boolean // 是否只執(zhí)行一次,Vue3.4+,默認(rèn)false
onTrack?: (event: DebuggerEvent) => void // 依賴(lài)追蹤時(shí)觸發(fā)
onTrigger?: (event: DebuggerEvent) => void // 觸發(fā)更新時(shí)觸發(fā)
}
11.2 flush選項(xiàng)詳解
| 取值 | 執(zhí)行時(shí)機(jī) | 適用場(chǎng)景 |
|---|---|---|
| ‘pre’ | 組件更新前執(zhí)行 | 需要訪(fǎng)問(wèn)更新前的DOM |
| ‘post’ | 組件更新后執(zhí)行 | 需要訪(fǎng)問(wèn)更新后的DOM |
| ‘sync’ | 同步執(zhí)行 | 需要立即響應(yīng)數(shù)據(jù)變化 |
// 示例:在DOM更新后執(zhí)行
watch(
count,
() => {
// 此時(shí)可以獲取到更新后的DOM
console.log('DOM高度:', document.getElementById('content').offsetHeight)
},
{ flush: 'post' }
)
十二、TypeScript高級(jí)用法
12.1 類(lèi)型定義與推斷
import { ref, watch, reactive } from 'vue'
// 1. 監(jiān)聽(tīng)ref
const count = ref<number>(0)
watch<number>(count, (newVal, oldVal) => {
// newVal和oldVal都會(huì)被推斷為number類(lèi)型
})
// 2. 監(jiān)聽(tīng)reactive對(duì)象
interface User {
name: string
age: number
}
const user = reactive<User>({ name: '張三', age: 20 })
watch(
() => user.age,
(newAge: number, oldAge: number) => {
// 顯式指定類(lèi)型
}
)
// 3. 監(jiān)聽(tīng)多個(gè)源
watch<[number, string]>(
[count, () => user.name],
([newCount, newName], [oldCount, oldName]) => {
// 類(lèi)型安全
}
)
12.2 自定義監(jiān)聽(tīng)器類(lèi)型
import { ref, watch, WatchSource } from 'vue'
// 定義通用監(jiān)聽(tīng)器類(lèi)型
type CustomWatcher<T> = (
source: WatchSource<T>,
callback: (newVal: T, oldVal: T) => void
) => void
// 創(chuàng)建帶類(lèi)型的監(jiān)聽(tīng)器
const numberWatcher: CustomWatcher<number> = (source, callback) => {
return watch(source, callback)
}
// 使用
const count = ref(0)
numberWatcher(count, (newVal, oldVal) => {
// 類(lèi)型安全
})
十三、調(diào)試與問(wèn)題診斷
13.1 使用Vue DevTools調(diào)試
Vue DevTools提供了專(zhuān)門(mén)的監(jiān)聽(tīng)器調(diào)試面板:
- 查看監(jiān)聽(tīng)器列表:在"Components"面板中選擇組件,查看"Watchers"部分
- 觸發(fā)時(shí)機(jī)分析:使用"Timeline"面板記錄監(jiān)聽(tīng)器觸發(fā)時(shí)間線(xiàn)
- 依賴(lài)可視化:查看每個(gè)監(jiān)聽(tīng)器依賴(lài)的響應(yīng)式數(shù)據(jù)
13.2 常見(jiàn)問(wèn)題診斷流程
監(jiān)聽(tīng)器不觸發(fā)時(shí)的排查步驟:
1. 確認(rèn)監(jiān)聽(tīng)源是響應(yīng)式數(shù)據(jù) - 使用isRef/isReactive檢查 - 確認(rèn)不是解構(gòu)后的值 2. 檢查監(jiān)聽(tīng)路徑是否正確 - 對(duì)象屬性需使用getter函數(shù) - 數(shù)組需監(jiān)聽(tīng)整個(gè)數(shù)組或使用正確索引 3. 驗(yàn)證數(shù)據(jù)確實(shí)發(fā)生了變化 - 使用console.log打印數(shù)據(jù) - 確認(rèn)不是引用類(lèi)型數(shù)據(jù)的內(nèi)部變化 4. 檢查是否需要深度監(jiān)聽(tīng) - 對(duì)象/數(shù)組內(nèi)部變化需設(shè)置deep: true 5. 檢查是否有條件執(zhí)行問(wèn)題 - watchEffect中是否有條件語(yǔ)句導(dǎo)致依賴(lài)未被訪(fǎng)問(wèn)
十四、性能優(yōu)化高級(jí)技巧
14.1 精確監(jiān)聽(tīng)避免過(guò)度觸發(fā)
// 不好的做法:監(jiān)聽(tīng)整個(gè)對(duì)象
watch(
user,
() => {
// 即使無(wú)關(guān)屬性變化也會(huì)觸發(fā)
},
{ deep: true }
)
// 好的做法:只監(jiān)聽(tīng)需要的屬性
watch(
() => ({ name: user.name, age: user.age }),
({ name, age }) => {
// 只有這兩個(gè)屬性變化才觸發(fā)
}
)
14.2 監(jiān)聽(tīng)器防抖與節(jié)流
import { ref, watch } from 'vue'
import { debounce, throttle } from 'lodash'
// 防抖示例
const searchInput = ref('')
const debouncedSearch = debounce((value) => {
// 搜索邏輯
}, 300)
watch(searchInput, debouncedSearch)
// 節(jié)流示例
const scrollPosition = ref(0)
const throttledScroll = throttle((position) => {
// 滾動(dòng)處理邏輯
}, 100)
watch(scrollPosition, throttledScroll)
14.3 大數(shù)據(jù)場(chǎng)景優(yōu)化
對(duì)于大型列表或復(fù)雜對(duì)象,使用shallowRef和shallowReactive減少響應(yīng)式開(kāi)銷(xiāo):
import { shallowRef, watch } from 'vue'
// 大型數(shù)據(jù)使用shallowRef
const largeList = shallowRef([])
// 只監(jiān)聽(tīng)引用變化,不監(jiān)聽(tīng)內(nèi)部屬性
watch(largeList, () => {
// 只有當(dāng)整個(gè)列表被替換時(shí)才觸發(fā)
})
十五、Vue2遷移指南
15.1 watch選項(xiàng)式API與組合式API對(duì)比
| Vue2選項(xiàng)式API | Vue3組合式API |
|---|---|
javascript watch: { count(newVal, oldVal) { // 處理邏輯 } } | javascript watch(count, (newVal, oldVal) => { // 處理邏輯 }) |
javascript watch: { 'user.name'(newVal) { // 監(jiān)聽(tīng)嵌套屬性 } } | javascript watch(() => user.name, (newVal) => { // 監(jiān)聽(tīng)嵌套屬性 }) |
javascript watch: { user: { handler: () => {}, deep: true } } | javascript watch(user, () => {}, { deep: true }) |
15.2 遷移注意事項(xiàng)
- this綁定:Vue3組合式API中沒(méi)有this,直接使用響應(yīng)式變量
- 深度監(jiān)聽(tīng):Vue3中reactive對(duì)象默認(rèn)深度監(jiān)聽(tīng),ref對(duì)象需要設(shè)置deep: true
- 數(shù)組監(jiān)聽(tīng):Vue3中直接修改數(shù)組索引可以觸發(fā)監(jiān)聽(tīng)(得益于Proxy)
- **watch方法∗∗:實(shí)例方法‘this.watch方法**:實(shí)例方法`this.watch方法∗∗:實(shí)例方法‘this.watch
替換為watch`函數(shù)
十六、單元測(cè)試示例
使用Jest測(cè)試監(jiān)聽(tīng)器行為:
import { ref, watch, watchEffect } from 'vue'
import { mount } from '@vue/test-utils'
describe('watch測(cè)試', () => {
it('基本監(jiān)聽(tīng)功能', async () => {
const count = ref(0)
const mockCallback = jest.fn()
watch(count, mockCallback)
// 初始狀態(tài)不觸發(fā)
expect(mockCallback).not.toHaveBeenCalled()
// 修改值后觸發(fā)
count.value = 1
await Promise.resolve() // 等待微任務(wù)完成
expect(mockCallback).toHaveBeenCalledWith(1, 0)
})
it('watchEffect立即執(zhí)行', () => {
const mockEffect = jest.fn()
watchEffect(mockEffect)
// 立即執(zhí)行
expect(mockEffect).toHaveBeenCalled()
})
})
十七、總結(jié):監(jiān)聽(tīng)器選擇決策指南
面對(duì)不同場(chǎng)景,如何選擇合適的監(jiān)聽(tīng)器API:
┌─────────────────────────────────────────────┐ │ 需要知道數(shù)據(jù)變化前后的值 │ │ ↓ │ │ 需要明確指定監(jiān)聽(tīng)源 │ │ ↓ ┌───────────────┐ │ │ 是────────────?│ watch │?─────────┐ │ ↓ └───────────────┘ │ │ 否 │ │ ↓ ┌───────────────┐ │ │ 需要自動(dòng)收集依賴(lài) ─────────────?│watchEffect│?─────────┐ │ └───────────────┘ │ │ │ │ 需要緩存計(jì)算結(jié)果 ─────────────?│ computed │ └─────────────────────────────────────────────┘
最終建議:
- 簡(jiǎn)單場(chǎng)景優(yōu)先使用
watchEffect,減少樣板代碼 - 需要精確控制時(shí)使用
watch - 純數(shù)據(jù)轉(zhuǎn)換使用
computed - 始終考慮性能影響,避免過(guò)度監(jiān)聽(tīng)
- 復(fù)雜邏輯拆分到組合式函數(shù)中管理監(jiān)聽(tīng)器
通過(guò)這一章節(jié)的補(bǔ)充,您現(xiàn)在應(yīng)該對(duì)Vue3的監(jiān)聽(tīng)器系統(tǒng)有了全面且深入的理解,能夠在各種場(chǎng)景下選擇合適的API并編寫(xiě)高效、可維護(hù)的代碼。
到此這篇關(guān)于Vue3中watch與watchEffect使用方法的文章就介紹到這了,更多相關(guān)Vue3 watch與watchEffect詳解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Vue3中Watch、Watcheffect、Computed的使用和區(qū)別解析
- vue3 watch和watchEffect的使用以及有哪些區(qū)別
- Vue3.0監(jiān)聽(tīng)器watch與watchEffect詳解
- vue3中的watch和watchEffect實(shí)例詳解
- 淺談Vue3中watchEffect的具體用法
- Vue3?中?watch?與?watchEffect?區(qū)別及用法小結(jié)
- Vue3中watchEffect高級(jí)偵聽(tīng)器的具體使用
- VUE3中watch和watchEffect的用法詳解
- 一文搞懂Vue3中watchEffect偵聽(tīng)器的使用
- Vue3中watchEffect和watch的基礎(chǔ)應(yīng)用詳解
相關(guān)文章
ElementUI年份范圍選擇器功能實(shí)現(xiàn)
elementUI中有日期范圍組件,月份范圍選擇的,就是沒(méi)有年份范圍選擇的,需要加一個(gè)類(lèi)似風(fēng)格的,下面這篇文章主要給大家介紹了關(guān)于ElementUI年份范圍選擇器功能實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2023-02-02
vue 插值 v-once,v-text, v-html詳解
這篇文章主要介紹了vue 插值 v-once,v-text, v-html詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
vue項(xiàng)目引入百度地圖BMapGL鼠標(biāo)繪制和BMap輔助工具
這篇文章主要為大家介紹了vue項(xiàng)目引入百度地圖BMapGL鼠標(biāo)繪制和BMap輔助工具的踩坑分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
Vue 菜單欄點(diǎn)擊切換單個(gè)class(高亮)的方法
今天小編就為大家分享一篇Vue 菜單欄點(diǎn)擊切換單個(gè)class(高亮)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
Vue3使用router,params傳參為空問(wèn)題
這篇文章主要介紹了Vue3使用router,params傳參為空問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04
詳解vue-template-admin三級(jí)路由無(wú)法緩存的解決方案
這篇文章主要介紹了vue-template-admin三級(jí)路由無(wú)法緩存的解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03

