新聞中心
曾經(jīng)以為自己會用 watch? 、 watchEffect 了,后來發(fā)現(xiàn)只是略懂皮毛。最近我就把 Vue3 的偵聽器全面梳理了一下,分享給大家??纯从袥]有你不會的吧,一起學起來!

Watch
基本用法
當我們需要在數(shù)據(jù)變化時執(zhí)行一些“副作用”:如更改 DOM、執(zhí)行異步操作,我們可以使用 watch 函數(shù):
{{ answer }}
watch() 一共可以接受三個參數(shù),偵聽數(shù)據(jù)源、回調(diào)函數(shù)和配置選項。
偵聽數(shù)據(jù)源
watch 的第一個參數(shù)可以是不同形式的“數(shù)據(jù)源”,它可以是:
- 一個 ref
- 一個計算屬性
- 一個 getter 函數(shù)(有返回值的函數(shù))
- 一個響應式對象
- 以上類型的值組成的數(shù)組
const x = ref(1)
const y = ref(1)
const doubleX = computed(() => x.value * 2)
const obj = reactive({ count: 0 })
// 單個 ref
watch(x, (newValue) => {
console.log(`x is ${newValue}`)
})
// 計算屬性
watch(doubleX, (newValue) => {
console.log(`doubleX is ${newValue}`)
})
// getter 函數(shù)
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
// 響應式對象
watch(obj, (newValue, oldValue) => {
// 在嵌套的屬性變更時觸發(fā)
// 注意:`newValue` 此處和 `oldValue` 是相等的
// 因為它們是同一個對象!
})
// 以上類型的值組成的數(shù)組
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
注意,你不能直接偵聽響應式對象的屬性值,例如:
const obj = reactive({ count: 0 })
// 錯誤,因為 watch() 得到的參數(shù)是一個 number
watch(obj.count, (count) => {
console.log(`count is: ${count}`)
})這里需要用一個返回該屬性的 getter 函數(shù):
// 提供一個 getter 函數(shù)
watch(
() => obj.count,
(count) => {
console.log(`count is: ${count}`)
}
)
回調(diào)函數(shù)
watch 的第二個參數(shù)是數(shù)據(jù)發(fā)生變化時執(zhí)行的回調(diào)函數(shù)。
這個回調(diào)函數(shù)接受三個參數(shù):新值、舊值,以及一個用于清理副作用的回調(diào)函數(shù)。該回調(diào)函數(shù)會在副作用下一次執(zhí)行前調(diào)用,可以用來清除無效的副作用,如等待中的異步請求:
const id = ref(1)
const data = ref(null)
watch(id, async (newValue, oldValue, onCleanup) => {
const { response, cancel } = doAsyncWork(id.value)
// `cancel` 會在 `id` 更改時調(diào)用
// 以便取消之前未完成的請求
onCleanup(cancel)
data.value = await response.json()
})
watch 的返回值是一個用來停止該副作用的函數(shù):
const unwatch = watch(() => {})
// ...當該偵聽器不再需要時
unwatch()注意:使用同步語句創(chuàng)建的偵聽器,會自動綁定到宿主組件實例上,并且會在宿主組件卸載時自動停止。使用異步回調(diào)創(chuàng)建一個偵聽器,則不會綁定到當前組件上,你必須手動停止它,以防內(nèi)存泄漏。如下面這個例子:
配置選項
watch 的第三個參數(shù)是一個可選的對象,支持以下選項:
- immediate:在偵聽器創(chuàng)建時立即觸發(fā)回調(diào)。
- deep:深度遍歷,以便在深層級變更時觸發(fā)回調(diào)。
- flush:回調(diào)函數(shù)的觸發(fā)時機。pre:默認,dom 更新前調(diào)用,post: dom 更新后調(diào)用,sync 同步調(diào)用。
- onTrack / onTrigger:用于調(diào)試的鉤子。在依賴收集和回調(diào)函數(shù)觸發(fā)時被調(diào)用。
- 深層偵聽器
直接給 watch() 傳入一個響應式對象,會默認創(chuàng)建一個深層偵聽器 —— 所有嵌套的屬性變更時都會被觸發(fā):
const obj = reactive({ count: 0 })
watch(obj, (newValue, oldValue) => {
// 在嵌套的屬性變更時觸發(fā)
// 注意:`newValue` 此處和 `oldValue` 是相等的
// 因為它們是同一個對象!
})
obj.count++相比之下,一個返回響應式對象的 getter 函數(shù),只有在對象被替換時才會觸發(fā):
const obj = reactive({
someString: 'hello',
someObject: { count: 0 }
})
watch(
() => obj.someObject,
() => {
// 僅當 obj.someObject 被替換時觸發(fā)
}
)當然,你也可以顯式地加上 deep 選項,強制轉(zhuǎn)成深層偵聽器:
watch(
() => obj.someObject,
(newValue, oldValue) => {
// `newValue` 此處和 `oldValue` 是相等的
// 除非 obj.someObject 被整個替換了
console.log('deep', newValue.count, oldValue.count)
},
{ deep: true }
)
obj.someObject.count++ // deep 1 1
深層偵聽一個響應式對象或數(shù)組,新值和舊值是相等的。為了解決這個問題,我們可以對值進行深拷貝。
watch(
() => _.cloneDeep(obj.someObject),
(newValue, oldValue) => {
// 此時 `newValue` 此處和 `oldValue` 是不相等的
console.log('deep', newValue.count, oldValue.count)
},
{ deep: true }
)
obj.someObject.count++ // deep 1 0
注意:深層偵聽需要遍歷所有嵌套的屬性,當數(shù)據(jù)結(jié)構(gòu)龐大時,開銷很大。所以我們要謹慎使用,并且留意性能。
watchEffect
watch()? 是懶執(zhí)行的:當數(shù)據(jù)源發(fā)生變化時,才會執(zhí)行回調(diào)。但在某些場景中,我們希望在創(chuàng)建偵聽器時,立即執(zhí)行一遍回調(diào)。當然使用 immediate 選項也能實現(xiàn):
const url = ref('https://...')
const data = ref(null)
async function fetchData() {
const response = await fetch(url.value)
data.value = await response.json()
}
// 立即執(zhí)行一次,再偵聽 url 變化
watch(url, fetchData, { immediate: true })可以看到 watch? 用到了三個參數(shù),我們可以用 watchEffect? 來簡化上面的代碼。watchEffect 會立即執(zhí)行一遍回調(diào)函數(shù),如果這時函數(shù)產(chǎn)生了副作用,Vue 會自動追蹤副作用的依賴關(guān)系,自動分析出偵聽數(shù)據(jù)源。上面的例子可以重寫為:
const url = ref('https://...')
const data = ref(null)
// 一個參數(shù)就可以搞定
watchEffect(async () => {
const response = await fetch(url.value)
data.value = await response.json()
})watchEffect? 接受兩個參數(shù),第一個參數(shù)是數(shù)據(jù)發(fā)生變化時執(zhí)行的回調(diào)函數(shù),用法和 watch? 一樣。第二個參數(shù)是一個可選的對象,支持 flush? 和 onTrack / onTrigger? 選項,功能和 watch 相同。
注意:watchEffect? 僅會在其同步執(zhí)行期間,才追蹤依賴。使用異步回調(diào)時,只有在第一個 await 之前訪問到的依賴才會被追蹤。
watch? vs. watchEffect
watch? 和 watchEffect 的主要功能是相同的,都能響應式地執(zhí)行回調(diào)函數(shù)。它們的區(qū)別是追蹤響應式依賴的方式不同:
- watch? 只追蹤明確定義的數(shù)據(jù)源,不會追蹤在回調(diào)中訪問到的東西;默認情況下,只有在數(shù)據(jù)源發(fā)生改變時才會觸發(fā)回調(diào);watch 可以訪問偵聽數(shù)據(jù)的新值和舊值。
- watchEffect? 會初始化執(zhí)行一次,在副作用發(fā)生期間追蹤依賴,自動分析出偵聽數(shù)據(jù)源;watchEffect 無法訪問偵聽數(shù)據(jù)的新值和舊值。
簡單一句話,watch? 功能更加強大,而 watchEffect 在某些場景下更加簡潔。
本文題目:終于徹底搞懂Watch、WatchEffect了,原來功能如此強大!
本文路徑:http://fisionsoft.com.cn/article/copccpo.html


咨詢
建站咨詢
