欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue3源碼分析偵聽(tīng)器watch的實(shí)現(xiàn)原理

 更新時(shí)間:2022年08月09日 16:19:52   作者:紫圣  
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),這篇文章主要介紹了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)文章

  • vue中如何使用vue-touch插件

    vue中如何使用vue-touch插件

    這篇文章主要介紹了vue中使用vue-touch插件的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-07-07
  • 關(guān)于Vue新搭檔TypeScript快速入門實(shí)踐

    關(guān)于Vue新搭檔TypeScript快速入門實(shí)踐

    這篇文章主要介紹了關(guān)于Vue新搭檔TypeScript快速入門實(shí)踐,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • Vant picker選擇器設(shè)置默認(rèn)值導(dǎo)致選擇器失效的解決

    Vant picker選擇器設(shè)置默認(rèn)值導(dǎo)致選擇器失效的解決

    這篇文章主要介紹了Vant picker選擇器設(shè)置默認(rèn)值導(dǎo)致選擇器失效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • Vue3?props的使用示例詳解

    Vue3?props的使用示例詳解

    這篇文章主要介紹了Vue3?props的使用詳解,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-10-10
  • Vue?element-ui中表格過(guò)長(zhǎng)內(nèi)容隱藏顯示的實(shí)現(xiàn)方式

    Vue?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-09
  • Vue基礎(chǔ)語(yǔ)法知識(shí)梳理下篇

    Vue基礎(chǔ)語(yǔ)法知識(shí)梳理下篇

    這篇文章主要介紹了Vue基礎(chǔ)語(yǔ)法知識(shí)梳理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-12-12
  • vue中@click綁定事件點(diǎn)擊不生效的原因及解決方案

    vue中@click綁定事件點(diǎn)擊不生效的原因及解決方案

    根據(jù)Vue2.0官方文檔關(guān)于父子組件通訊的原則,父組件通過(guò)prop傳遞數(shù)據(jù)給子組件,子組件觸發(fā)事件給父組件,這篇文章主要介紹了vue中@click綁定事件點(diǎn)擊不生效的解決方案,需要的朋友可以參考下
    2022-12-12
  • vue router動(dòng)態(tài)路由設(shè)置參數(shù)可選問(wèn)題

    vue 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)(增刪改查)

    這篇文章主要介紹了利用vue3+ts實(shí)現(xiàn)管理后臺(tái)(增刪改查),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • VUE3中h()函數(shù)和createVNode()函數(shù)的使用解讀

    VUE3中h()函數(shù)和createVNode()函數(shù)的使用解讀

    這篇文章主要介紹了VUE3中h()函數(shù)和createVNode()函數(shù)的使用解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08

最新評(píng)論