vue3中watch和watchEffect的區(qū)別
watch和watchEffect
watchEffect()
官網(wǎng)介紹:立即運行一個函數(shù),同時響應式地追蹤其依賴,并在依賴更改時重新執(zhí)行。
類型
function watchEffect(
effect: (onCleanup: OnCleanup) => void,
options?: WatchEffectOptions
): StopHandle
?
type OnCleanup = (cleanupFn: () => void) => void
?
interface WatchEffectOptions {
flush?: 'pre' | 'post' | 'sync' // 默認:'pre'
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
}
?
type StopHandle = () => void詳細信息
- 第一個參數(shù)就是要運行的副作用函數(shù)。這個副作用函數(shù)的參數(shù)也是一個函數(shù),用來注冊清理回調(diào)。清理回調(diào)會在該副作用下一次執(zhí)行前被調(diào)用,可以用來清理無效的副作用,例如等待中的異步請求 (參見下面的示例)。
- 第二個參數(shù)是一個可選的選項,可以用來調(diào)整副作用的刷新時機或調(diào)試副作用的依賴。
默認情況下,偵聽器將在組件渲染之前執(zhí)行。設置 flush: 'post' 將會使偵聽器延遲到組件渲染之后再執(zhí)行。詳見回調(diào)的觸發(fā)時機。在某些特殊情況下 (例如要使緩存失效),可能有必要在響應式依賴發(fā)生改變時立即觸發(fā)偵聽器。這可以通過設置 flush: 'sync' 來實現(xiàn)。然而,該設置應謹慎使用,因為如果有多個屬性同時更新,這將導致一些性能和數(shù)據(jù)一致性的問題。
返回值是一個用來停止該副作用的函數(shù)。
示例
const count = ref(0) watchEffect(() => console.log(count.value)) // -> 輸出 0 count.value++ // -> 輸出 1
副作用清除:
watchEffect(async (onCleanup) => {
const { response, cancel } = doAsyncWork(id.value)
// `cancel` 會在 `id` 更改時調(diào)用
// 以便取消之前
// 未完成的請求
onCleanup(cancel)
data.value = await response
})停止偵聽器:
const stop = watchEffect(() => {})
// 當不再需要此偵聽器時:
stop()選項:
watchEffect(() => {}, {
flush: 'post'
})功能及其使用官網(wǎng)寫的清楚,再來了解下它的原理
function watchEffect(
source: WatchEffect,
options?: WatchOptionsBase
): WatchStopHandle {
let getter = () => {
if (cleanup) {
cleanup()
}
return source(onCleanup)
}
let cleanup: () => void
let onCleanup: OnCleanup = (fn: () => void) => {
cleanup = effect.onStop = () => fn()
}
const job: SchedulerJob = () => {
if (!effect.active) {
return
}
effect.run()
}
let scheduler: EffectScheduler
if (flush === 'sync') {
scheduler = job as any
} else if (flush === 'post') {
scheduler = () => queuePostRenderEffect(job)
} else {
// default: 'pre'
scheduler = () => queuePreFlushCb(job)
}
// 創(chuàng)建 effect
const effect = new ReactiveEffect(getter, scheduler)
if (flush === 'post') {
queuePostRenderEffect(
effect.run.bind(effect)
)
} else {
effect.run()
}
return () => {
effect.stop()
}
}watchEffect的回調(diào)函數(shù)就是一個副作用函數(shù),因為我們使用watchEffect就是偵聽到依賴的變化后執(zhí)行某些操作。什么是副作用(side effect),簡單的說副作用就是執(zhí)行某種操作,如對外部可變數(shù)據(jù)或變量的修改,外部接口的調(diào)用等。
watchEffect內(nèi)部是創(chuàng)建了一個ReactiveEffect對象,接收兩個參數(shù),第一個getter,在執(zhí)行effect.run()時會調(diào)用,第二個scheduler在依賴變化后執(zhí)行。
那我們執(zhí)行watchEffect(() => console.log(count))時,具體做了什么呢,首先創(chuàng)建了個getter,執(zhí)行getter做了兩件事,第一個會看有cleanup沒,cleanup會在調(diào)用OnCleanup賦值, 有就執(zhí)行,第二個執(zhí)行source,為watchEffect傳遞的回調(diào)函數(shù),參數(shù)為OnCleanup;然后會創(chuàng)建OnCleanup,這個會在執(zhí)行source時當參數(shù)傳入,當在source里執(zhí)行這個參數(shù),會創(chuàng)建cleanup,這個cleanup的執(zhí)行時機是下一次執(zhí)行前被調(diào)用或停止該副作用時調(diào)用;然后創(chuàng)建scheduler,scheduler主要就是執(zhí)行effect.run()也就是getter,會根據(jù)傳的flush的不同會創(chuàng)建三種執(zhí)行時機不同的scheduler 第一種sync scheduler:為異步執(zhí)行,會在依賴改變時立即執(zhí)行,比如for循環(huán)改變10次依賴就會執(zhí)行10次 第二種 post scheduler:會在組件渲染后執(zhí)行 第三種 pre scheduler:默認,會在組件渲染前執(zhí)行 然后會創(chuàng)建ReactiveEffect,參數(shù)為之前創(chuàng)建getter和scheduler,創(chuàng)建完后接著會根據(jù)傳的flush來執(zhí)行effect.run(),是post會延遲在組件渲染后執(zhí)行,否則就立即執(zhí)行,執(zhí)行effect.run()會觸發(fā)依賴手收集, 最后返回停止該副作用的函數(shù),執(zhí)行會執(zhí)行effect.stop(),這個會觸發(fā)cleanup
wath()
官網(wǎng)介紹:偵聽一個或多個響應式數(shù)據(jù)源,并在數(shù)據(jù)源變化時調(diào)用所給的回調(diào)函數(shù)。
類型
// 偵聽單個來源
function watch<T>(
source: WatchSource<T>,
callback: WatchCallback<T>,
options?: WatchOptions
): StopHandle
// 偵聽多個來源
function watch<T>(
sources: WatchSource<T>[],
callback: WatchCallback<T[]>,
options?: WatchOptions
): StopHandle
type WatchCallback<T> = (
value: T,
oldValue: T,
onCleanup: (cleanupFn: () => void) => void
) => void
type WatchSource<T> =
| Ref<T> // ref
| (() => T) // getter
| T extends object
? T
: never // 響應式對象
interface WatchOptions extends WatchEffectOptions {
immediate?: boolean // 默認:false
deep?: boolean // 默認:false
flush?: 'pre' | 'post' | 'sync' // 默認:'pre'
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
}詳細信息
watch() 默認是懶偵聽的,即僅在偵聽源發(fā)生變化時才執(zhí)行回調(diào)函數(shù)。
第一個參數(shù)是偵聽器的源。這個來源可以是以下幾種:
- 一個函數(shù),返回一個值
- 一個 ref
- 一個響應式對象
- ...或是由以上類型的值組成的數(shù)組
第二個參數(shù)是在發(fā)生變化時要調(diào)用的回調(diào)函數(shù)。這個回調(diào)函數(shù)接受三個參數(shù):新值、舊值,以及一個用于注冊副作用清理的回調(diào)函數(shù)。該回調(diào)函數(shù)會在副作用下一次重新執(zhí)行前調(diào)用,可以用來清除無效的副作用,例如等待中的異步請求。
當偵聽多個來源時,回調(diào)函數(shù)接受兩個數(shù)組,分別對應來源數(shù)組中的新值和舊值。
第三個可選的參數(shù)是一個對象,支持以下這些選項:
- immediate:在偵聽器創(chuàng)建時立即觸發(fā)回調(diào)。第一次調(diào)用時舊值是 undefined。
- deep:如果源是對象,強制深度遍歷,以便在深層級變更時觸發(fā)回調(diào)。參考深層偵聽器。
- flush:調(diào)整回調(diào)函數(shù)的刷新時機。參考回調(diào)的刷新時機及 watchEffect()。
- onTrack / onTrigger:調(diào)試偵聽器的依賴。參考調(diào)試偵聽器。
與 watchEffect() 相比,watch() 使我們可以:
- 懶執(zhí)行副作用;
- 更加明確是應該由哪個狀態(tài)觸發(fā)偵聽器重新執(zhí)行;
- 可以訪問所偵聽狀態(tài)的前一個值和當前值。
示例
偵聽一個 getter 函數(shù):
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)偵聽一個 ref:
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})當偵聽多個來源時,回調(diào)函數(shù)接受兩個數(shù)組,分別對應來源數(shù)組中的新值和舊值:
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})當使用 getter 函數(shù)作為源時,回調(diào)只在此函數(shù)的返回值變化時才會觸發(fā)。如果你想讓回調(diào)在深層級變更時也能觸發(fā),你需要使用 { deep: true } 強制偵聽器進入深層級模式。在深層級模式時,如果回調(diào)函數(shù)由于深層級的變更而被觸發(fā),那么新值和舊值將是同一個對象。
const state = reactive({ count: 0 })
watch(
() => state,
(newValue, oldValue) => {
// newValue === oldValue
},
{ deep: true }
)當直接偵聽一個響應式對象時,偵聽器會自動啟用深層模式:
const state = reactive({ count: 0 })
watch(state, () => {
/* 深層級變更狀態(tài)所觸發(fā)的回調(diào) */
})watch的功能是完全包含watchEffect的,原理其實是差不多的,主要就是getter和scheduler不同
function watch(
source: WatchSource | WatchSource[] | WatchEffect | object,
cb: WatchCallback | null,
{ immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
): WatchStopHandle {
let getter: () => any
if (isRef(source)) {
getter = () => source.value
} else if (isReactive(source)) {
getter = () => source
deep = true
} else if (isArray(source)) {
getter = () =>
source.map(s => {
if (isRef(s)) {
return s.value
} else if (isReactive(s)) {
return traverse(s)
} else if (isFunction(s)) {
return s()
}
})
} else if (isFunction(source)) {
getter = () => source()
}
if (cb && deep) {
const baseGetter = getter
getter = () => traverse(baseGetter())
}
let cleanup: () => void
let onCleanup: OnCleanup = (fn: () => void) => {
cleanup = effect.onStop = () => {
fn()
}
}
let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE
const job: SchedulerJob = () => {
if (!effect.active) {
return
}
// watch(source, cb)
const newValue = effect.run()
if (cleanup) {
cleanup()
}
cb(newValue, oldValue, onCleanup)
oldValue = newValue
}
let scheduler: EffectScheduler
if (flush === 'sync') {
scheduler = job as any
} else if (flush === 'post') {
scheduler = () => queuePostRenderEffect(job)
} else {
// default: 'pre'
scheduler = () => queuePreFlushCb(job)
}
const effect = new ReactiveEffect(getter, scheduler)
if (immediate) {
job()
} else {
oldValue = effect.run()
}
?
return () => {
effect.stop()
}
}首先是getter,可以看到會根據(jù)不同source的類型定義不同的getter,包括Ref、Reactive、Array和function四種,getter就是獲取這些類型返回的值,從而觸發(fā)依賴收集;然后是deep,如果deep為true,會改變getter為() => traverse(baseGetter()),traverse()會遞歸獲取里面的值;onCleanup和watchEffect的一樣;scheduler的執(zhí)行時機和watchEffect一樣,主要就是job不一樣,job主要做兩件事,第一個是執(zhí)行cleanup和watchEffect一樣,第二個是執(zhí)行watch(source, cb)的第二個參數(shù)cb,參數(shù)為newValue和oldValue,newValue為effect.run()也就是getter()的返回值,oldValue為上次執(zhí)行的newValue;同樣也創(chuàng)建了ReactiveEffect對象;然后是immediate,為true執(zhí)行job,job里會執(zhí)行effect.run()收集依賴和cb,否則就執(zhí)行effect.run()收集依賴了;最后watch的返回值和watchEffect一樣。
總的來說watch和watchEffect功能很強大,源碼看起來也不是很難,看完源碼使用起來就更加得心應手了。
本文主要記錄看watch和watchEffect源碼的一些筆記,沒有涉及響應式的收集依賴和觸發(fā)。
到此這篇關于vue3中watch和watchEffect的區(qū)別的文章就介紹到這了,更多相關vue3 watch watchEffect內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- 詳解Vue3?中的watchEffect?特性
- vue3數(shù)據(jù)監(jiān)聽watch/watchEffect的示例代碼
- vue3中watch與watchEffect的區(qū)別
- vue3中watch和watchEffect實戰(zhàn)梳理
- Vue3中的?computed,watch,watchEffect的使用方法
- Vue3?中?watch?與?watchEffect?區(qū)別及用法小結(jié)
- vue3中的watch和watchEffect實例詳解
- 淺談Vue3中watchEffect的具體用法
- vue3的watch和watchEffect你了解嗎
- VUE3中watch和watchEffect的用法詳解
- Vue3中watchEffect的用途淺析
相關文章
VueJs路由跳轉(zhuǎn)——vue-router的使用詳解
本篇文章主要介紹了VueJs路由跳轉(zhuǎn)——vue-router的使用,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-01-01
vue Nprogress進度條功能實現(xiàn)常見問題
這篇文章主要介紹了vue Nprogress進度條功能實現(xiàn),NProgress是頁面跳轉(zhuǎn)是出現(xiàn)在瀏覽器頂部的進度條,本文通過實例代碼給大家講解,需要的朋友可以參考下2021-07-07
vue+mousemove實現(xiàn)鼠標拖動功能(拖動過快失效問題解決方法)
這篇文章主要介紹了vue+mousemove實現(xiàn)鼠標拖動功能,文中給大家介紹了鼠標移動過快拖動就失效問題的解決方法,需要的朋友可以參考下2018-08-08
vue3實戰(zhàn)教程之a(chǎn)xios的封裝和環(huán)境變量
這篇文章主要給大家介紹了關于vue3實戰(zhàn)教程之a(chǎn)xios的封裝和環(huán)境變量的相關資料,文中通過實例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2022-02-02
解決vue偵聽器watch,調(diào)用this時出現(xiàn)undefined的問題
這篇文章主要介紹了解決vue偵聽器watch,調(diào)用this時出現(xiàn)undefined的問題,具有很好的參考2020-10-10

