Vue3源碼分析偵聽(tīng)器watch的實(shí)現(xiàn)原理
watch 的本質(zhì)
所謂的watch,其本質(zhì)就是觀測(cè)一個(gè)響應(yīng)式數(shù)據(jù),當(dāng)數(shù)據(jù)發(fā)生變化時(shí)通知并執(zhí)行相應(yīng)的回調(diào)函數(shù)。實(shí)際上,watch 的實(shí)現(xiàn)本質(zhì)就是利用了 effect 和 options.scheduler 選項(xiàng)。如下例子所示:
// watch 函數(shù)接收兩個(gè)參數(shù),source 是響應(yīng)式數(shù)據(jù),cb 是回調(diào)函數(shù) function watch(source, cb){ effect( // 觸發(fā)讀取操作,從而建立聯(lián)系 () => source.foo, { scheduler(){ // 當(dāng)數(shù)據(jù)變化時(shí),調(diào)用回調(diào)函數(shù) cb cb() } } ) }
如上面的代碼所示嗎,source 是響應(yīng)式數(shù)據(jù),cb 是回調(diào)函數(shù)。如果副作用函數(shù)中存在 scheduler 選項(xiàng),當(dāng)響應(yīng)式數(shù)據(jù)發(fā)生變化時(shí),會(huì)觸發(fā) scheduler 函數(shù)執(zhí)行,而不是直接觸發(fā)副作用函數(shù)執(zhí)行。從這個(gè)角度來(lái)看, scheduler 調(diào)度函數(shù)就相當(dāng)于是一個(gè)回調(diào)函數(shù),而 watch 的實(shí)現(xiàn)就是利用了這點(diǎn)。
watch 的函數(shù)簽名
偵聽(tīng)多個(gè)源
偵聽(tīng)的數(shù)據(jù)源可以 是一個(gè)數(shù)組,如下面的函數(shù)簽名所示:
// packages/runtime-core/src/apiWatch.ts // 數(shù)據(jù)源是一個(gè)數(shù)組 // overload: array of multiple sources + cb export function watch< T extends MultiWatchSources, Immediate extends Readonly<boolean> = false >( sources: [...T], cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>, options?: WatchOptions<Immediate> ): WatchStopHandle
也可以使用數(shù)組同時(shí)偵聽(tīng)多個(gè)源,如下面的函數(shù)簽名所示:
// packages/runtime-core/src/apiWatch.ts // 使用數(shù)組同時(shí)偵聽(tīng)多個(gè)源 // overload: multiple sources w/ `as const` // watch([foo, bar] as const, () => {}) // somehow [...T] breaks when the type is readonly export function watch< T extends Readonly<MultiWatchSources>, Immediate extends Readonly<boolean> = false >( source: T, cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>, options?: WatchOptions<Immediate> ): WatchStopHandle
偵聽(tīng)單一源
偵聽(tīng)的數(shù)據(jù)源是一個(gè) ref 類型的數(shù)據(jù) 或者是一個(gè)具有返回值的 getter 函數(shù),如下面的函數(shù)簽名所示:
// packages/runtime-core/src/apiWatch.ts // 數(shù)據(jù)源是一個(gè) ref 類型的數(shù)據(jù) 或者是一個(gè)具有返回值的 getter 函數(shù) // overload: single source + cb export function watch<T, Immediate extends Readonly<boolean> = false>( source: WatchSource<T>, cb: WatchCallback<T, Immediate extends true ? T | undefined : T>, options?: WatchOptions<Immediate> ): WatchStopHandle export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
偵聽(tīng)的數(shù)據(jù)源是一個(gè)響應(yīng)式的 obj 對(duì)象,如下面的函數(shù)簽名所示:
// packages/runtime-core/src/apiWatch.ts // 數(shù)據(jù)源是一個(gè)響應(yīng)式的 obj 對(duì)象 // overload: watching reactive object w/ cb export function watch< T extends object, Immediate extends Readonly<boolean> = false >( source: T, cb: WatchCallback<T, Immediate extends true ? T | undefined : T>, options?: WatchOptions<Immediate> ): WatchStopHandle
watch 的實(shí)現(xiàn)
watch 函數(shù)
// packages/runtime-core/src/apiWatch.ts // implementation export function watch<T = any, Immediate extends Readonly<boolean> = false>( source: T | WatchSource<T>, cb: any, options?: WatchOptions<Immediate> ): WatchStopHandle { if (__DEV__ && !isFunction(cb)) { warn( `\`watch(fn, options?)\` signature has been moved to a separate API. ` + `Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` + `supports \`watch(source, cb, options?) signature.` ) } return doWatch(source as any, cb, options) }
可以看到,watch 函數(shù)接收3個(gè)參數(shù),分別是:source 偵聽(tīng)的數(shù)據(jù)源,cb 回調(diào)函數(shù),options 偵聽(tīng)選項(xiàng)。
source 參數(shù)
從watch的函數(shù)重載中可以知道,當(dāng)偵聽(tīng)的是單一源時(shí),source 可以是一個(gè) ref 類型的數(shù)據(jù) 或者是一個(gè)具有返回值的 getter 函數(shù),也可以是一個(gè)響應(yīng)式的 obj 對(duì)象。當(dāng)偵聽(tīng)的是多個(gè)源時(shí),source 可以是一個(gè)數(shù)組。
cb 參數(shù)
在 cb 回調(diào)函數(shù)中,給開(kāi)發(fā)者提供了最新的value,舊的value以及onCleanup函數(shù)用與清除副作用。如下面的類型定義所示:
export type WatchCallback<V = any, OV = any> = ( value: V, oldValue: OV, onCleanup: OnCleanup ) => any
options 參數(shù)
options 選項(xiàng)可以控制 watch 的行為,例如通過(guò)options的選項(xiàng)參數(shù)immediate來(lái)控制watch的回調(diào)是否立即執(zhí)行,通過(guò)options的選項(xiàng)參數(shù)來(lái)控制watch的回調(diào)函數(shù)是同步執(zhí)行還是異步執(zhí)行。options 參數(shù)的類型定義如下:
export interface WatchOptionsBase extends DebuggerOptions { flush?: 'pre' | 'post' | 'sync' } export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase { immediate?: Immediate deep?: boolean }
可以看到 options 的類型定義 WatchOptions 繼承了 WatchOptionsBase。也就是說(shuō),watch 的 options 中除了 immediate 和 deep 這兩個(gè)特有的參數(shù)外,還可以傳遞 WatchOptionsBase 中的所有參數(shù)以控制副作用執(zhí)行的行為。
在 watch 的函數(shù)體中調(diào)用了 doWatch 函數(shù),我們來(lái)看看它的實(shí)現(xiàn)。
doWatch 函數(shù)
實(shí)際上,無(wú)論是watch函數(shù),還是 watchEffect 函數(shù),在執(zhí)行時(shí)最終調(diào)用的都是 doWatch 函數(shù)。
doWatch 函數(shù)簽名
function doWatch( source: WatchSource | WatchSource[] | WatchEffect | object, cb: WatchCallback | null, { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ ): WatchStopHandle
doWatch 的函數(shù)簽名與 watch 的函數(shù)簽名基本一致,也是接收三個(gè)參數(shù)。在 doWatch 函數(shù)中,為了便于options 選項(xiàng)的使用,對(duì) options 進(jìn)行了解構(gòu)。
初始化變量
首先從 component 中獲取當(dāng)前的組件實(shí)例,然后分別定義三個(gè)變量。其中 getter 是一個(gè)函數(shù),她或作為副作用的函數(shù)參數(shù)傳入到副作用函數(shù)中。forceTrigger 變量是一個(gè)布爾值,用來(lái)標(biāo)識(shí)是否需要強(qiáng)制觸發(fā)副作用函數(shù)執(zhí)行。isMultiSource 變量同樣也是一個(gè)布爾值,用來(lái)標(biāo)記偵聽(tīng)的數(shù)據(jù)源是單一源還是以數(shù)組形式傳入的多個(gè)源,初始值為 false,表示偵聽(tīng)的是單一源。如下面的代碼所示:
const instance = currentInstance let getter: () => any // 是否需要強(qiáng)制觸發(fā)副作用函數(shù)執(zhí)行 let forceTrigger = false // 偵聽(tīng)的是否是多個(gè)源 let isMultiSource = false
接下來(lái)根據(jù)偵聽(tīng)的數(shù)據(jù)源來(lái)初始化這三個(gè)變量。
偵聽(tīng)的數(shù)據(jù)源是一個(gè) ref 類型的數(shù)據(jù)
當(dāng)偵聽(tīng)的數(shù)據(jù)源是一個(gè) ref 類型的數(shù)據(jù)時(shí),通過(guò)返回 source.value 來(lái)初始化 getter,也就是說(shuō),當(dāng) getter 函數(shù)被觸發(fā)時(shí),會(huì)通過(guò)source.value 獲取到實(shí)際偵聽(tīng)的數(shù)據(jù)。然后通過(guò) isShallow 函數(shù)來(lái)判斷偵聽(tīng)的數(shù)據(jù)源是否是淺響應(yīng),并將其結(jié)果賦值給 forceTrigger,完成 forceTrigger 變量的初始化。如下面的代碼所示:
if (isRef(source)) { // 偵聽(tīng)的數(shù)據(jù)源是 ref getter = () => source.value // 判斷數(shù)據(jù)源是否是淺響應(yīng) forceTrigger = isShallow(source) }
偵聽(tīng)的數(shù)據(jù)源是一個(gè)響應(yīng)式數(shù)據(jù)
當(dāng)偵聽(tīng)的數(shù)據(jù)源是一個(gè)響應(yīng)式數(shù)據(jù)時(shí),直接返回 source 來(lái)初始化 getter ,即 getter 函數(shù)被觸發(fā)時(shí)直接返回 偵聽(tīng)的數(shù)據(jù)源。由于響應(yīng)式數(shù)據(jù)中可能會(huì)是一個(gè)object 對(duì)象,因此將 deep 設(shè)置為 true,在觸發(fā) getter 函數(shù)時(shí)可以遞歸地讀取對(duì)象的屬性值。如下面的代碼所示:
else if (isReactive(source)) { // 偵聽(tīng)的數(shù)據(jù)源是響應(yīng)式數(shù)據(jù) getter = () => source deep = true }
偵聽(tīng)的數(shù)據(jù)源是一個(gè)數(shù)組
當(dāng)偵聽(tīng)的數(shù)據(jù)源是一個(gè)數(shù)組,即同時(shí)偵聽(tīng)多個(gè)源。此時(shí)直接將 isMultiSource 變量設(shè)置為 true,表示偵聽(tīng)的是多個(gè)源。接著通過(guò)數(shù)組的 some 方法來(lái)檢測(cè)偵聽(tīng)的多個(gè)源中是否存在響應(yīng)式對(duì)象,將其結(jié)果賦值給 forceTrigger 。然后遍歷數(shù)組,判斷每個(gè)源的類型,從而完成 getter 函數(shù)的初始化。如下面的代碼所示:
else if (isArray(source)) { // 偵聽(tīng)的數(shù)據(jù)源是一個(gè)數(shù)組,即同時(shí)偵聽(tīng)多個(gè)源 isMultiSource = true forceTrigger = source.some(isReactive) getter = () => // 遍歷數(shù)組,判斷每個(gè)源的類型 source.map(s => { if (isRef(s)) { // 偵聽(tīng)的數(shù)據(jù)源是 ref return s.value } else if (isReactive(s)) { // 偵聽(tīng)的數(shù)據(jù)源是響應(yīng)式數(shù)據(jù) return traverse(s) } else if (isFunction(s)) { // 偵聽(tīng)的數(shù)據(jù)源是一個(gè)具有返回值的 getter 函數(shù) return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER) } else { __DEV__ && warnInvalidSource(s) } }) }
偵聽(tīng)的數(shù)據(jù)源是一個(gè)函數(shù)
當(dāng)偵聽(tīng)的數(shù)據(jù)源是一個(gè)具有返回值的 getter 函數(shù)時(shí),判斷 doWatch 函數(shù)的第二個(gè)參數(shù) cb 是否有傳入。如果有傳入,則處理的是 watch 函數(shù)的場(chǎng)景,此時(shí)執(zhí)行 source 函數(shù),將執(zhí)行結(jié)果賦值給 getter 。如果沒(méi)有傳入,則處理的是 watchEffect 函數(shù)的場(chǎng)景。在該場(chǎng)景下,如果組件實(shí)例已經(jīng)卸載,則直接返回,不執(zhí)行 source 函數(shù)。否則就執(zhí)行 cleanup 清除依賴,然后執(zhí)行 source 函數(shù),將執(zhí)行結(jié)果賦值給 getter 。如下面的代碼所示:
else if (isFunction(source)) { // 處理 watch 和 watchEffect 的場(chǎng)景 // watch 的第二個(gè)參數(shù)可以是一個(gè)具有返回值的 getter 參數(shù),第二個(gè)參數(shù)是一個(gè)回調(diào)函數(shù) // watchEffect 的參數(shù)是一個(gè) 函數(shù) // 偵聽(tīng)的數(shù)據(jù)源是一個(gè)具有返回值的 getter 函數(shù) if (cb) { // getter with cb // 處理的是 watch 的場(chǎng)景 // 執(zhí)行 source 函數(shù),將執(zhí)行結(jié)果賦值給 getter getter = () => callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER) } else { // no cb -> simple effect // 沒(méi)有回調(diào),即為 watchEffect 的場(chǎng)景 getter = () => { // 件實(shí)例已經(jīng)卸載,則不執(zhí)行,直接返回 if (instance && instance.isUnmounted) { return } // 清除依賴 if (cleanup) { cleanup() } // 執(zhí)行 source 函數(shù) return callWithAsyncErrorHandling( source, instance, ErrorCodes.WATCH_CALLBACK, [onCleanup] ) } } }
遞歸讀取響應(yīng)式數(shù)據(jù)
如果偵聽(tīng)的數(shù)據(jù)源是一個(gè)響應(yīng)式數(shù)據(jù),需要遞歸讀取響應(yīng)式數(shù)據(jù)中的屬性值。如下面的代碼所示:
// 處理的是 watch 的場(chǎng)景 // 遞歸讀取對(duì)象的屬性值 if (cb && deep) { const baseGetter = getter getter = () => traverse(baseGetter()) }
在上面的代碼中,doWatch 函數(shù)的第二個(gè)參數(shù) cb 有傳入,說(shuō)明處理的是 watch 中的場(chǎng)景。deep 變量為 true ,說(shuō)明此時(shí)偵聽(tīng)的數(shù)據(jù)源是一個(gè)響應(yīng)式數(shù)據(jù),因此需要調(diào)用 traverse 函數(shù)來(lái)遞歸讀取數(shù)據(jù)源中的每個(gè)屬性,對(duì)其進(jìn)行監(jiān)聽(tīng),從而當(dāng)任意屬性發(fā)生變化時(shí)都能夠觸發(fā)回調(diào)函數(shù)執(zhí)行。
定義清除副作用函數(shù)
聲明 cleanup 和 onCleanup 函數(shù),并在 onCleanup 函數(shù)的執(zhí)行過(guò)程中給 cleanup 函數(shù)賦值,當(dāng)副作用函數(shù)執(zhí)行一些異步的副作用時(shí),這些響應(yīng)需要在其失效是清除。如下面的代碼所示:
// 清除副作用函數(shù) let cleanup: () => void let onCleanup: OnCleanup = (fn: () => void) => { cleanup = effect.onStop = () => { callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP) } }
封裝 scheduler 調(diào)度函數(shù)
為了便于控制 watch 的回調(diào)函數(shù) cb 的執(zhí)行時(shí)機(jī),需要將 scheduler 調(diào)度函數(shù)封裝為一個(gè)獨(dú)立的 job 函數(shù),如下面的代碼所示:
// 將 scheduler 調(diào)度函數(shù)封裝為一個(gè)獨(dú)立的 job 函數(shù),便于在初始化和變更時(shí)執(zhí)行它 const job: SchedulerJob = () => { if (!effect.active) { return } if (cb) { // 處理 watch 的場(chǎng)景 // watch(source, cb) // 執(zhí)行副作用函數(shù)獲取新值 const newValue = effect.run() // 如果數(shù)據(jù)源是響應(yīng)式數(shù)據(jù)或者需要強(qiáng)制觸發(fā)副作用函數(shù)執(zhí)行或者新舊值發(fā)生了變化 // 則執(zhí)行回調(diào)函數(shù),并更新舊值 if ( deep || forceTrigger || (isMultiSource ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])[i]) ) : hasChanged(newValue, oldValue)) || (__COMPAT__ && isArray(newValue) && isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)) ) { // 當(dāng)回調(diào)再次執(zhí)行前先清除副作用 // cleanup before running cb again if (cleanup) { cleanup() } // 執(zhí)行watch 函數(shù)的回調(diào)函數(shù) cb,將舊值和新值作為回調(diào)函數(shù)的參數(shù) callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [ newValue, // 首次調(diào)用時(shí),將 oldValue 的值設(shè)置為 undefined // pass undefined as the old value when it's changed for the first time oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue, onCleanup ]) // 更新舊值,不然下一次會(huì)得到錯(cuò)誤的舊值 oldValue = newValue } } else { // watchEffect // 處理 watchEffect 的場(chǎng)景 effect.run() } }
在 job 函數(shù)中,判斷回調(diào)函數(shù) cb 是否傳入,如果有傳入,那么是 watch 函數(shù)被調(diào)用的場(chǎng)景,否則就是 watchEffect 函數(shù)被調(diào)用的場(chǎng)景。
如果是 watch 函數(shù)被調(diào)用的場(chǎng)景,首先執(zhí)行副作用函數(shù),將執(zhí)行結(jié)果賦值給 newValue 變量,作為最新的值。然后判斷需要執(zhí)行回調(diào)函數(shù) cb 的情況:
- 如果偵聽(tīng)的數(shù)據(jù)源是響應(yīng)式數(shù)據(jù),需要深度偵聽(tīng),即 deep 為 true
- 如果需要強(qiáng)制觸發(fā)副作用函數(shù)執(zhí)行,即 forceTrigger 為 true
- 如果新舊值發(fā)生了變化
只要滿足上面三種情況中的其中一種,就需要執(zhí)行 watch 函數(shù)的回調(diào)函數(shù) cb。如果回調(diào)函數(shù) cb 是再次執(zhí)行,在執(zhí)行之前需要先清除副作用。然后調(diào)用 callWithAsyncErrorHandling 函數(shù)執(zhí)行回調(diào)函數(shù)cb,并將新值newValue 和舊值 oldValue 傳入回調(diào)函數(shù)cb中。在回調(diào)函數(shù)cb執(zhí)行后,更新舊值oldValue,避免在下一次執(zhí)行回調(diào)函數(shù)cb時(shí)獲取到錯(cuò)誤的舊值。
如果是 watchEffect 函數(shù)被調(diào)用的場(chǎng)景,則直接執(zhí)行副作用函數(shù)即可。
設(shè)置 job 的 allowRecurse 屬性
根據(jù)是否傳入回調(diào)函數(shù)cb,設(shè)置 job 函數(shù)的 allowRecurse 屬性。這個(gè)設(shè)置十分重要,它能夠讓 job 作為偵聽(tīng)器的回調(diào),這樣調(diào)度器就能知道它允許調(diào)用自身。
// important: mark the job as a watcher callback so that scheduler knows // it is allowed to self-trigger (#1727) // 重要:讓調(diào)度器任務(wù)作為偵聽(tīng)器的回調(diào)以至于調(diào)度器能知道它可以被允許自己派發(fā)更新 job.allowRecurse = !!cb
flush 選項(xiàng)指定回調(diào)函數(shù)的執(zhí)行時(shí)機(jī)
在調(diào)用 watch 函數(shù)時(shí),可以通過(guò) options 的 flush 選項(xiàng)來(lái)指定回調(diào)函數(shù)的執(zhí)行時(shí)機(jī):
當(dāng) flush 的值為 sync 時(shí),代表調(diào)度器函數(shù)是同步執(zhí)行,此時(shí)直接將 job 賦值給 scheduler,這樣調(diào)度器函數(shù)就會(huì)直接執(zhí)行。
當(dāng) flush 的值為 post 時(shí),代表調(diào)度函數(shù)需要將副作用函數(shù)放到一個(gè)微任務(wù)隊(duì)列中,并等待 DOM 更新結(jié)束后再執(zhí)行。
當(dāng) flush 的值為 pre 時(shí),即調(diào)度器函數(shù)默認(rèn)的執(zhí)行方式,這時(shí)調(diào)度器會(huì)區(qū)分組件是否已經(jīng)掛載。如果組件未掛載,則先執(zhí)行一次調(diào)度函數(shù),即執(zhí)行回調(diào)函數(shù)cb。在組件掛載之后,將調(diào)度函數(shù)推入一個(gè)優(yōu)先執(zhí)行時(shí)機(jī)的隊(duì)列中。
// 這里處理的是回調(diào)函數(shù)的執(zhí)行時(shí)機(jī)
let scheduler: EffectScheduler if (flush === 'sync') { // 同步執(zhí)行,將 job 直接賦值給調(diào)度器 scheduler = job as any // the scheduler function gets called directly } else if (flush === 'post') { // 將調(diào)度函數(shù) job 添加到微任務(wù)隊(duì)列中執(zhí)行 scheduler = () => queuePostRenderEffect(job, instance && instance.suspense) } else { // default: 'pre' // 調(diào)度器函數(shù)默認(rèn)的執(zhí)行模式 scheduler = () => { if (!instance || instance.isMounted) { // 組件掛載后將 job 推入一個(gè)優(yōu)先執(zhí)行時(shí)機(jī)的隊(duì)列中 queuePreFlushCb(job) } else { // with 'pre' option, the first call must happen before // the component is mounted so it is called synchronously. // 在 pre 選型中,第一次調(diào)用必須發(fā)生在組件掛載之前 // 所以這次調(diào)用是同步的 job() } } }
創(chuàng)建副作用函數(shù)
初始化完 getter 函數(shù)和調(diào)度器函數(shù) scheduler 后,調(diào)用 ReactiveEffect 類來(lái)創(chuàng)建一個(gè)副作用函數(shù)
// 創(chuàng)建一個(gè)副作用函數(shù) const effect = new ReactiveEffect(getter, scheduler)
執(zhí)行副作用函數(shù)
在執(zhí)行副作用函數(shù)之前,首先判斷是否傳入了回調(diào)函數(shù)cb,如果有傳入,則根據(jù) options 的 immediate 選項(xiàng)來(lái)判斷是否需要立即執(zhí)行回調(diào)函數(shù)cb,如果指定了immediate 選項(xiàng),則立即執(zhí)行 job 函數(shù),即 watch 的回調(diào)函數(shù)會(huì)在 watch 創(chuàng)建時(shí)立即執(zhí)行一次。否則就手動(dòng)調(diào)用副作用函數(shù),并將返回值作為舊值,賦值給 oldValue。如下面的代碼所示:
if (cb) { // 選項(xiàng)參數(shù) immediate 來(lái)指定回調(diào)是否需要立即執(zhí)行 if (immediate) { // 回調(diào)函數(shù)會(huì)在 watch 創(chuàng)建時(shí)立即執(zhí)行一次 job() } else { // 手動(dòng)調(diào)用副作用函數(shù),拿到的就是舊值 oldValue = effect.run() } }
如果 options 的 flush 選項(xiàng)的值為 post ,需要將副作用函數(shù)放入到微任務(wù)隊(duì)列中,等待組件掛載完成后再執(zhí)行副作用函數(shù)。如下面的代碼所示:
else if (flush === 'post') { // 在調(diào)度器函數(shù)中判斷 flush 是否為 'post',如果是,將其放到微任務(wù)隊(duì)列中執(zhí)行 queuePostRenderEffect( effect.run.bind(effect), instance && instance.suspense ) }
其余情況都是立即執(zhí)行副作用函數(shù)。如下面的代碼所示:
else { // 其余情況立即首次執(zhí)行副作用 effect.run() }
返回匿名函數(shù),停止偵聽(tīng)
doWatch 函數(shù)最后返回了一個(gè)匿名函數(shù),該函數(shù)用以結(jié)束數(shù)據(jù)源的偵聽(tīng)。因此在調(diào)用 watch 或者 watchEffect 時(shí),可以調(diào)用其返回值類結(jié)束偵聽(tīng)。
return () => { effect.stop() if (instance && instance.scope) { // 返回一個(gè)函數(shù),用以顯式的結(jié)束偵聽(tīng) remove(instance.scope.effects!, effect) } }
總結(jié)
watch 的本質(zhì)就是觀測(cè)一個(gè)響應(yīng)式數(shù)據(jù),當(dāng)數(shù)據(jù)發(fā)生變化時(shí)通知并執(zhí)行相應(yīng)的回調(diào)函數(shù)。watch的實(shí)現(xiàn)利用了effect 和 options.scheduler 選項(xiàng)。
watch 可以偵聽(tīng)單一源,也可以偵聽(tīng)多個(gè)源。偵聽(tīng)單一源時(shí)數(shù)據(jù)源可以是一個(gè)具有返回值的getter 函數(shù),或者是一個(gè) ref 對(duì)象,也可以是一個(gè)響應(yīng)式的 object 對(duì)象。偵聽(tīng)多個(gè)源時(shí),其數(shù)據(jù)源是一個(gè)數(shù)組。
在watch的實(shí)現(xiàn)中,根據(jù)偵聽(tīng)的數(shù)據(jù)源的類型來(lái)初始化getter 函數(shù)和 scheduler 調(diào)度函數(shù),根據(jù)這兩個(gè)函數(shù)創(chuàng)建一個(gè)副作用函數(shù),并根據(jù) options 的 immediate 選項(xiàng)以及 flush 選項(xiàng)來(lái)指定回調(diào)函數(shù)和副作用函數(shù)的執(zhí)行時(shí)機(jī)。當(dāng) immediate 為 true 時(shí),在watch 創(chuàng)建時(shí)會(huì)立即執(zhí)行一次回調(diào)函數(shù)。當(dāng) flush 的值為 post 時(shí),scheduler 調(diào)度函數(shù)和副作用函數(shù)都會(huì)被添加到微任務(wù)隊(duì)列中,會(huì)等待 DOM 更新結(jié)束后再執(zhí)行。
到此這篇關(guān)于Vue3源碼分析偵聽(tīng)器watch的實(shí)現(xiàn)原理的文章就介紹到這了,更多相關(guān)Vue3偵聽(tīng)器watch內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于Vue新搭檔TypeScript快速入門實(shí)踐
這篇文章主要介紹了關(guān)于Vue新搭檔TypeScript快速入門實(shí)踐,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09Vant picker選擇器設(shè)置默認(rèn)值導(dǎo)致選擇器失效的解決
這篇文章主要介紹了Vant picker選擇器設(shè)置默認(rèn)值導(dǎo)致選擇器失效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01Vue?element-ui中表格過(guò)長(zhǎng)內(nèi)容隱藏顯示的實(shí)現(xiàn)方式
在Vue項(xiàng)目中,使用ElementUI渲染表格數(shù)據(jù)時(shí),如果某一個(gè)列數(shù)值長(zhǎng)度超過(guò)列寬,會(huì)默認(rèn)換行,造成顯示不友好,下面這篇文章主要給大家介紹了關(guān)于Vue?element-ui中表格過(guò)長(zhǎng)內(nèi)容隱藏顯示的實(shí)現(xiàn)方式,需要的朋友可以參考下2022-09-09vue中@click綁定事件點(diǎn)擊不生效的原因及解決方案
根據(jù)Vue2.0官方文檔關(guān)于父子組件通訊的原則,父組件通過(guò)prop傳遞數(shù)據(jù)給子組件,子組件觸發(fā)事件給父組件,這篇文章主要介紹了vue中@click綁定事件點(diǎn)擊不生效的解決方案,需要的朋友可以參考下2022-12-12vue router動(dòng)態(tài)路由設(shè)置參數(shù)可選問(wèn)題
這篇文章主要介紹了vue-router動(dòng)態(tài)路由設(shè)置參數(shù)可選,文中給大家提到了vue-router 動(dòng)態(tài)添加 路由的方法,需要的朋友可以參考下2019-08-08利用vue3+ts實(shí)現(xiàn)管理后臺(tái)(增刪改查)
這篇文章主要介紹了利用vue3+ts實(shí)現(xiàn)管理后臺(tái)(增刪改查),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10VUE3中h()函數(shù)和createVNode()函數(shù)的使用解讀
這篇文章主要介紹了VUE3中h()函數(shù)和createVNode()函數(shù)的使用解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08