【vue3源碼】五箫荡、watch源碼解析

【vue3源碼】五魁亦、watch源碼解析

參考代碼版本:vue 3.2.37

官方文檔:https://vuejs.org/

watch用來監(jiān)聽特定數(shù)據(jù)源,并在單獨(dú)的回調(diào)函數(shù)中執(zhí)行副作用羔挡。默認(rèn)是惰性的——即回調(diào)僅在偵聽源發(fā)生變化時(shí)被調(diào)用洁奈。
文件位置:packages/runtime-core/src/apiWatch.ts

使用示例

監(jiān)聽一個(gè)getter函數(shù):

const state = reactive({ count: 0 })
watch(
  () => state.count,
  (newVal, oldVal) => {
    //... 
  }
)

監(jiān)聽一個(gè)ref

const count = ref(0)
watch(
  count,
  (newVal, oldVal) => {
    //... 
  }
)

監(jiān)聽多個(gè)數(shù)據(jù)源:

const foo = ref('')
const bar = ref('')
watch(
  [ foo, bar ],
  ([ newFoo, newBar ], [ oldFoo, oldBar ]) => {
    // ...
  }
)

深度監(jiān)聽:

const state = reactive({ count: 0 })
watch(
  () => state,
  () => {
    // ...
  },
  { deep: true }
)

// or
watch(state, () => {
  // ...
})

源碼分析

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接收三個(gè)參數(shù):source監(jiān)聽的源、cb回調(diào)函數(shù)绞灼、options監(jiān)聽配置利术,watch函數(shù)返回一個(gè)停止監(jiān)聽函數(shù)。镀赌。

watch中調(diào)用了一個(gè)叫做doWatch的函數(shù)氯哮,與watch作用相似的watchEffect际跪、watchPostEffect商佛、watchSyncEffect內(nèi)部也都使用了這個(gè)doWatch函數(shù)。

export function watchEffect(
  effect: WatchEffect,
  options?: WatchOptionsBase
): WatchStopHandle {
  return doWatch(effect, null, options)
}

export function watchPostEffect(
  effect: WatchEffect,
  options?: DebuggerOptions
) {
  return doWatch(
    effect,
    null,
    (__DEV__
      ? Object.assign(options || {}, { flush: 'post' })
      : { flush: 'post' }) as WatchOptionsBase
  )
}

export function watchSyncEffect(
  effect: WatchEffect,
  options?: DebuggerOptions
) {
  return doWatch(
    effect,
    null,
    (__DEV__
      ? Object.assign(options || {}, { flush: 'sync' })
      : { flush: 'sync' }) as WatchOptionsBase
  )
}

可見doWatchwatch API的核心姆打,接下來重點(diǎn)研究doWatch的實(shí)現(xiàn)良姆。

doWatch

doWatch源碼過長,這里就不搬運(yùn)了幔戏,在分析過程中玛追,會展示相關(guān)代碼。

doWatch函數(shù)接收三個(gè)參數(shù):source監(jiān)聽的數(shù)據(jù)源闲延,cb回調(diào)函數(shù)痊剖,options:監(jiān)聽配置。doWatch返回一個(gè)停止監(jiān)聽函數(shù)垒玲。

function doWatch(
  source: WatchSource | WatchSource[] | WatchEffect | object,
  cb: WatchCallback | null,
  { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
): WatchStopHandle {
  // ...
}

首先需要對immediate陆馁、deep做校驗(yàn),如果cbnull合愈,immediate叮贩、deep不為undefined進(jìn)行提示击狮。

if (__DEV__ && !cb) {
  if (immediate !== undefined) {
    warn(
      `watch() "immediate" option is only respected when using the ` +
        `watch(source, callback, options?) signature.`
    )
  }
  if (deep !== undefined) {
    warn(
      `watch() "deep" option is only respected when using the ` +
        `watch(source, callback, options?) signature.`
    )
  }
}

緊接著聲明了一些變量:

const warnInvalidSource = (s: unknown) => {
  warn(
    `Invalid watch source: `,
    s,
    `A watch source can only be a getter/effect function, a ref, ` +
      `a reactive object, or an array of these types.`
  )
}

// 當(dāng)前組件實(shí)例
const instance = currentInstance
// 副作用函數(shù),在初始化effect時(shí)使用
let getter: () => any
// 強(qiáng)制觸發(fā)監(jiān)聽
let forceTrigger = false
// 是否為多數(shù)據(jù)源益老。
let isMultiSource = false

然后根據(jù)傳入的soure確定getter彪蓬、forceTriggerisMultiSource捺萌。這里分了5個(gè)分支:

  • 如果sourceref類型档冬,getter是個(gè)返回source.value的函數(shù),forceTrigger取決于source是否是淺層響應(yīng)式互婿。
if (isRef(source)) {
  getter = () => source.value
  forceTrigger = isShallow(source)
}
  • 如果sourcereactive類型捣郊,getter是個(gè)返回source的函數(shù),并將deep設(shè)置為true慈参。
if (isReactive(source)) {
  getter = () => source
  deep = true
}
  • 如果source是個(gè)數(shù)組呛牲,將isMultiSource設(shè)為trueforceTrigger取決于source是否有reactive類型的數(shù)據(jù)驮配,getter函數(shù)中會遍歷source娘扩,針對不同類型的source做不同處理。
if (isArray(source)) {
  isMultiSource = true
  forceTrigger = source.some(isReactive)
  getter = () =>
    source.map(s => {
      if (isRef(s)) {
        return s.value
      } else if (isReactive(s)) {
        return traverse(s)
      } else if (isFunction(s)) {
        return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
      } else {
        __DEV__ && warnInvalidSource(s)
      }
    })
}
  • 如果source是個(gè)function壮锻。存在cb的情況下琐旁,getter函數(shù)中會執(zhí)行source,這里source會通過callWithErrorHandling函數(shù)執(zhí)行猜绣,在callWithErrorHandling中會處理source執(zhí)行過程中出現(xiàn)的錯(cuò)誤灰殴;不存在cb的話,在getter中掰邢,如果組件已經(jīng)被卸載了牺陶,直接return,否則判斷cleanupcleanup是在watchEffect中通過onCleanup注冊的清理函數(shù))辣之,如果存在cleanup執(zhí)行cleanup掰伸,接著執(zhí)行source,并返回執(zhí)行結(jié)果怀估。source會被callWithAsyncErrorHandling包裝狮鸭,該函數(shù)作用會處理source執(zhí)行過程中出現(xiàn)的錯(cuò)誤,與callWithErrorHandling不同的是多搀,callWithAsyncErrorHandling會處理異步錯(cuò)誤歧蕉。
if (isFunction(source)) {
  if (cb) {
    getter = () =>
      callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
  } else {
    // watchEffect
    getter = () => {
      // 如果組件實(shí)例已經(jīng)卸載,直接return
      if (instance && instance.isUnmounted) {
        return
      }
      // 如果清理函數(shù)康铭,則執(zhí)行清理函數(shù)
      if (cleanup) {
        cleanup()
      }
      // 執(zhí)行source惯退,傳入onCleanup,用來注冊清理函數(shù)
      return callWithAsyncErrorHandling(
        source,
        instance,
        ErrorCodes.WATCH_CALLBACK,
        [onCleanup]
      )
    }
  }
}

callWithErrorHandling函數(shù)可以接收四個(gè)參數(shù):fn待執(zhí)行的函數(shù)麻削、instance組件實(shí)例蒸痹、typefn執(zhí)行過程中出現(xiàn)的錯(cuò)誤類型春弥、argsfn執(zhí)行所需的參數(shù)。

export function callWithErrorHandling(
  fn: Function,
  instance: ComponentInternalInstance | null,
  type: ErrorTypes,
  args?: unknown[]
) {
  let res
  try {
    res = args ? fn(...args) : fn()
  } catch (err) {
    handleError(err, instance, type)
  }
  return res
}

callWithAsyncErrorHandling的參數(shù)與callWithErrorHandling類似叠荠,與callWithErrorHandling不同的是匿沛,callWithAsyncErrorHandling可以接受一個(gè)fn數(shù)組。

export function callWithAsyncErrorHandling(
  fn: Function | Function[],
  instance: ComponentInternalInstance | null,
  type: ErrorTypes,
  args?: unknown[]
): any[] {
  if (isFunction(fn)) {
    const res = callWithErrorHandling(fn, instance, type, args)
    if (res && isPromise(res)) {
      res.catch(err => {
        handleError(err, instance, type)
      })
    }
    return res
  }

  const values = []
  for (let i = 0; i < fn.length; i++) {
    values.push(callWithAsyncErrorHandling(fn[i], instance, type, args))
  }
  return values
}
  • 其他情況榛鼎,getter會被賦為一個(gè)空函數(shù)
getter = NOOP
__DEV__ && warnInvalidSource(source)

接下來會對vue2的數(shù)組的進(jìn)行兼容性處理逃呼,breaking-changes/watch

if (__COMPAT__ && cb && !deep) {
  const baseGetter = getter
  getter = () => {
    const val = baseGetter()
    if (
      isArray(val) &&
      checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)
    ) {
      traverse(val)
    }
    return val
  }
}

如果存在cb并且deeptrue,那么需要對數(shù)據(jù)進(jìn)行深度監(jiān)聽者娱,這時(shí)抡笼,會重新對getter賦值,在新的getter函數(shù)中遞歸訪問之前getter的返回結(jié)果黄鳍。

if (cb && deep) {
  const baseGetter = getter
  getter = () => traverse(baseGetter())
}

traverse實(shí)現(xiàn)推姻,遞歸遍歷所有屬性,seen用于防止循環(huán)引用問題框沟。

export function traverse(value: unknown, seen?: Set<unknown>) {
  // 如果value不是對象或value不可被轉(zhuǎn)為代理(經(jīng)過markRaw處理)藏古,直接return value
  if (!isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
    return value
  }
  // sean用于暫存訪問過的屬性,防止出現(xiàn)循環(huán)引用的問題
  // 如:
  // const obj = { a: 1 }
  // obj.b = obj
  seen = seen || new Set()
  // 如果seen中已經(jīng)存在了value忍燥,意味著value中存在循環(huán)引用的情況拧晕,這時(shí)return value
  if (seen.has(value)) {
    return value
  }
  // 添加value到seen中
  seen.add(value)
  // 如果是ref,遞歸訪問value.value
  if (isRef(value)) {
    traverse(value.value, seen)
  } else if (isArray(value)) { // 如果是數(shù)組梅垄,遍歷數(shù)組并調(diào)用traverse遞歸訪問元素內(nèi)的屬性
    for (let i = 0; i < value.length; i++) {
      traverse(value[i], seen)
    }
  } else if (isSet(value) || isMap(value)) { // 如果是Set或Map厂捞,調(diào)用traverse遞歸訪問集合中的值
    value.forEach((v: any) => {
      traverse(v, seen)
    })
  } else if (isPlainObject(value)) { // 如果是原始對象,調(diào)用traverse遞歸方位value中的屬性
    for (const key in value) {
      traverse((value as any)[key], seen)
    }
  }
  // 最后需要返回value
  return value
}

到此队丝,getter函數(shù)(getter函數(shù)中會盡可能訪問響應(yīng)式數(shù)據(jù)靡馁,尤其是deeptrue并存在cb的情況時(shí),會調(diào)用traverse完成對source的遞歸屬性訪問)炭玫、forceTrigger奈嘿、isMultiSource已經(jīng)被確定貌虾,接下來聲明了兩個(gè)變量:cleanup吞加、onCleanuponCleanup會作為參數(shù)傳遞給watchEffect中的effect函數(shù)尽狠。當(dāng)onCleanup執(zhí)行時(shí)衔憨,會將他的參數(shù)通過callWithErrorHandling封裝賦給cleanupeffect.onStopeffect在后文中創(chuàng)建)。

let cleanup: () => void
let onCleanup: OnCleanup = (fn: () => void) => {
  cleanup = effect.onStop = () => {
    callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
  }
}

緊接著是一段SSR處理過程:

if (__SSR__ && isInSSRComponentSetup) {
  // we will also not call the invalidate callback (+ runner is not set up)
  onCleanup = NOOP
  if (!cb) {
    getter()
  } else if (immediate) {
    callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
      getter(),
      isMultiSource ? [] : undefined,
      onCleanup
    ])
  }
  return NOOP
}

然后聲明了一個(gè)oldValuejob變量袄膏。如果是多數(shù)據(jù)源oldValue是個(gè)數(shù)組,否則是個(gè)對象。

job函數(shù)的作用是觸發(fā)cb(watch)或執(zhí)行effect.run(watchEffect)嗡午。job函數(shù)中會首先判斷effect的激活狀態(tài),如果未激活德崭,則return。然后判斷如果存在cb揖盘,調(diào)用effet.run獲取最新值眉厨,下一步就是觸發(fā)cb,這里觸發(fā)cb需要滿足以下條件的任意一個(gè)條件即可:

  1. 深度監(jiān)聽deep===true
  2. 強(qiáng)制觸發(fā)forceTrigger===true
  3. 如果多數(shù)據(jù)源兽狭,newValue中存在與oldValue中的值不相同的項(xiàng)(利用Object.is判斷)憾股;如果不是多數(shù)據(jù)源,newValueoldValue不相同箕慧。
  4. 開啟了vue2兼容模式服球,并且newValue是個(gè)數(shù)組,并且開啟了WATCH_ARRAY

只要符合上述條件的任意一條颠焦,便可已觸發(fā)cb斩熊,在觸發(fā)cb之前會先調(diào)用cleanup函數(shù)。執(zhí)行完cb后伐庭,需要將newValue賦值給oldValue座享。

如果不存在cb,那么直接調(diào)用effect.run即可似忧。

let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE
const job: SchedulerJob = () => {
  if (!effect.active) {
    return
  }
  if (cb) {
    const newValue = effect.run()
    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))
    ) {
      if (cleanup) {
        cleanup()
      }
      callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
        newValue,
        // 如果oldValue為INITIAL_WATCHER_VALUE渣叛,說明是第一次watch,那么oldValue是undefined
        oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
        onCleanup
      ])
      oldValue = newValue
    }
  } else {
    effect.run()
  }
}
job.allowRecurse = !!cb

接下來聲明了一個(gè)調(diào)度器scheduler盯捌,在scheduler中會根據(jù)flush的不同決定job的觸發(fā)時(shí)機(jī):

let scheduler: EffectScheduler
if (flush === 'sync') {
  scheduler = job as any 
} else if (flush === 'post') {
  // 延遲執(zhí)行淳衙,將job添加到一個(gè)延遲隊(duì)列,這個(gè)隊(duì)列會在組件掛在后饺著、更新的生命周期中執(zhí)行
  scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
} else {
  // 默認(rèn) pre箫攀,將job添加到一個(gè)優(yōu)先執(zhí)行隊(duì)列,該隊(duì)列在掛載前執(zhí)行
  scheduler = () => {
    if (!instance || instance.isMounted) {
      queuePreFlushCb(job)
    } else {
      job()
    }
  }
}

此時(shí)幼衰,getterscheduler準(zhǔn)備完成靴跛,創(chuàng)建effect實(shí)例。

const effect = new ReactiveEffect(getter, scheduler)

創(chuàng)建effect實(shí)例后渡嚣,開始首次執(zhí)行副作用函數(shù)梢睛。這里針對不同情況有多個(gè)分支:

  • 如果存在cb的情況
    • 如果immediatetrue,執(zhí)行job识椰,觸發(fā)cb
    • 否則執(zhí)行effect.run()進(jìn)行依賴的收集绝葡,并將結(jié)果賦值給oldValue
  • 如果flush===post,會將effect.run推入一個(gè)延遲隊(duì)列中
  • 其他情況腹鹉,也就是watchEffect藏畅,則會執(zhí)行effect.run進(jìn)行依賴的收集
if (cb) {
  if (immediate) {
    job()
  } else {
    oldValue = effect.run()
  }
} else if (flush === 'post') {
  queuePostRenderEffect(
    effect.run.bind(effect),
    instance && instance.suspense
  )
} else {
  effect.run()
}

最后,返回一個(gè)函數(shù)功咒,這個(gè)函數(shù)的作用是停止watch對數(shù)據(jù)源的監(jiān)聽愉阎。在函數(shù)內(nèi)部調(diào)用effect.stop()effect置為失活狀態(tài)绞蹦,如果存在組件實(shí)例,并且組件示例中存在effectScope榜旦,那么需要將effecteffectScope中移除坦辟。

return () => {
  effect.stop()
  if (instance && instance.scope) {
    remove(instance.scope.effects!, effect)
  }
}

watchEffect、watchSyncEffect章办、watchPostEffect

watchEffect锉走、watchSyncEffectwatchPostEffect的實(shí)現(xiàn)均是通過doWatch實(shí)現(xiàn)藕届。

export function watchEffect(
  effect: WatchEffect,
  options?: WatchOptionsBase
): WatchStopHandle {
  return doWatch(effect, null, options)
}

export function watchPostEffect(
  effect: WatchEffect,
  options?: DebuggerOptions
) {
  return doWatch(
    effect,
    null,
    (__DEV__
      ? Object.assign(options || {}, { flush: 'post' })
      : { flush: 'post' }) as WatchOptionsBase
  )
}

export function watchSyncEffect(
  effect: WatchEffect,
  options?: DebuggerOptions
) {
  return doWatch(
    effect,
    null,
    (__DEV__
      ? Object.assign(options || {}, { flush: 'sync' })
      : { flush: 'sync' }) as WatchOptionsBase
  )
}

watch與watchEffect的區(qū)別

watch只會追蹤在source中明確的數(shù)據(jù)源挪蹭,不會追蹤回調(diào)函數(shù)中訪問到的東西。而且只在數(shù)據(jù)源發(fā)生變化后觸發(fā)回調(diào)休偶。watch會避免在發(fā)生副作用時(shí)追蹤依賴(當(dāng)發(fā)生副作用時(shí)梁厉,會執(zhí)行調(diào)度器,在調(diào)度器中會將job推入不同的任務(wù)隊(duì)列踏兜,達(dá)到控制回調(diào)函數(shù)的觸發(fā)時(shí)機(jī)的目的)词顾,因此,我們能更加精確地控制回調(diào)函數(shù)的觸發(fā)時(shí)機(jī)碱妆。

watchEffect肉盹,會在副作用發(fā)生期間追蹤依賴。它會在同步執(zhí)行過程中疹尾,自動(dòng)追蹤所有能訪問到的響應(yīng)式property

示例分析

為了更好地理解watchwatchEffect的流程上忍,我們以下面幾個(gè)例子來理解watchwatchEffect

例1

const state = reactive({ str: 'foo', obj: { num: 1 } })
const flag = ref(true)

watch(
  [ flag, () => state.obj ],
  ([ newFlag, newObj ], [ oldFlag, oldObj ]) => {
    console.log(newFlag)
    console.log(newObj.num)
    console.log(oldFlag)
    console.log(oldObj && oldObj.num)
  },
  {
    immediate: true,
    flush: 'sync'
  }
)

state.obj.num = 2

state.obj = {
  num: 2
}

watch中調(diào)用doWatch方法纳本,在doWatch會構(gòu)造getter函數(shù)窍蓝,因?yàn)樗O(jiān)聽的數(shù)據(jù)源是個(gè)數(shù)組,所以getter函數(shù)返回值也是個(gè)數(shù)組繁成,因?yàn)閿?shù)據(jù)源的第一項(xiàng)是個(gè)ref吓笙,所以getter返回值第一項(xiàng)是ref.value,數(shù)據(jù)源的第二項(xiàng)是個(gè)function巾腕,所以getter返回值第二項(xiàng)是() => state.obj的返回值面睛,也就是state.obj,由于我們未指定depp祠墅,最終生成的getter() => [ref.value, state.obj]侮穿。

然后利用getterscheduler生成effect歌径,因?yàn)槲覀冎付?code>immediate: true毁嗦,所以會立即執(zhí)行job函數(shù),在job函數(shù)中回铛,會執(zhí)行effect.run()(這個(gè)過程中最終執(zhí)行getter函數(shù)狗准,而在執(zhí)行getter函數(shù)的過程中會被對應(yīng)響應(yīng)式對象的proxy所攔截克锣,進(jìn)而收集依賴),然后將effect.run()的結(jié)果賦值給newValue腔长。然后對位比較newValueoldValue中的元素袭祟,因?yàn)?code>oldValue此時(shí)是個(gè)空數(shù)組,所以會觸發(fā)cb捞附,在cb觸發(fā)過程中將newValue巾乳、oldValue依次傳入,此時(shí)打印true 1 undefined undefined鸟召,當(dāng)cb執(zhí)行完胆绊,將newValue賦值為oldValue

當(dāng)執(zhí)行state.obj.num = 2時(shí)欧募。因?yàn)樵谏弦淮蔚囊蕾囀占^程中(也就是getter執(zhí)行過程中)压状,并沒有訪問到num屬性,也就不會收集它的依賴跟继,所以該步驟不會影響到watch种冬。

當(dāng)state.obj = { num: 2 }時(shí),會觸發(fā)到obj對應(yīng)的依賴舔糖,而在依賴觸發(fā)過程中會執(zhí)行調(diào)度器娱两,因?yàn)?code>flush為sync,所以調(diào)度器就是job金吗,當(dāng)執(zhí)行job時(shí)谷婆,通過effect.run()得到newValue,因?yàn)檫@時(shí)oldValue中的state.valuenewValue中的state.value已經(jīng)不是同一個(gè)對象了辽聊,所以觸發(fā)cb纪挎。打印true 2 true 2

為什么第二次打印newObj.numoldObj.num相同跟匆?因?yàn)?code>oldValue中的oldObj保存的是state.obj的引用地址异袄,一旦state.obj發(fā)生改變,oldValue也會對應(yīng)改變玛臂。

例2

const state = reactive({ str: 'foo', obj: { num: 1 } })
const flag = ref(true)

watchEffect(() => {
  console.log(flag.value)
  console.log(state.obj.num)
})


state.obj.num = 2

state.obj = {
  num: 3
}

與例1相同烤蜕,例2先生成gettergetter中會調(diào)用source)與scheduler,然后生成effect迹冤。因?yàn)?code>watchEffect是沒有cb參數(shù)讽营,也未指定flush,所以會直接執(zhí)行effct.run()泡徙。在effect.run執(zhí)行過程中橱鹏,會調(diào)用source,在source執(zhí)行過程中會將effect收集到flag.deptargetMap[toRaw(state)].objtargetMap[toRaw(state).obj].num中莉兰。所以第一次打印true 1挑围。

當(dāng)執(zhí)行state.obj.num = 2,會觸發(fā)targetMap[toRaw(state).obj].num中的依賴糖荒,也就是effect杉辙,在觸發(fā)依賴過程中會執(zhí)行effect.scheduler,將job推入一個(gè)pendingPreFlushCbs隊(duì)列中捶朵。

當(dāng)執(zhí)行state.obj = { num: 3 }蜘矢,會觸發(fā)targetMap[toRaw(state)].obj中的依賴,也就是effect综看,在觸發(fā)依賴過程中會執(zhí)行effect.scheduler硼端,將job推入一個(gè)pendingPreFlushCbs隊(duì)列中。

最后會執(zhí)行pendingPreFlushCbs隊(duì)列中的job寓搬,在執(zhí)行之前會對pendingPreFlushCbs進(jìn)行去重珍昨,也就是說最后只會執(zhí)行一個(gè)job。最終打印true 3句喷。

總結(jié)

watch镣典、watchEffectwatchSyncEffect唾琼、watchPostEffect的實(shí)現(xiàn)均是通過一個(gè)doWatch函數(shù)實(shí)現(xiàn)兄春。

dowatch中會首先生成一個(gè)getter函數(shù)。如果是watchAPI锡溯,那么這個(gè)getter函數(shù)中會根據(jù)傳入?yún)?shù)赶舆,訪問監(jiān)聽數(shù)據(jù)源中的屬性(可能會遞歸訪問對象中的屬性,取決于deep)祭饭,并返回與數(shù)據(jù)源數(shù)據(jù)類型一致的數(shù)據(jù)(如果數(shù)據(jù)源是ref類型芜茵,getter函數(shù)返回ref.value;如果數(shù)據(jù)源類型是reactive倡蝙,getter函數(shù)返回值也是reactive九串;如果數(shù)據(jù)源是數(shù)組,那么getter函數(shù)返回值也應(yīng)該是數(shù)組寺鸥;如果數(shù)據(jù)源是函數(shù)類型猪钮,那么getter函數(shù)返回值是數(shù)據(jù)源的返回值)。如果是watchEffect等API胆建,那么getter函數(shù)中會執(zhí)行source函數(shù)烤低。

然后定義一個(gè)job函數(shù)。如果是watch笆载,job函數(shù)中會執(zhí)行effect.run獲取新的值扑馁,并比較新舊值涯呻,是否執(zhí)行cb;如果是watchEffect等API檐蚜,job中執(zhí)行effect.run魄懂。那么如何只監(jiān)聽到state.obj.num的變換呢沿侈?

當(dāng)聲明完job闯第,會緊跟著定義一個(gè)調(diào)度器,這個(gè)調(diào)度器的作用是根據(jù)flushjob放到不同的任務(wù)隊(duì)列中缀拭。

然后根據(jù)getter調(diào)度器scheduler初始化一個(gè)ReactiveEffect`實(shí)例咳短。

接著進(jìn)行初始化:如果是watch,如果是立即執(zhí)行蛛淋,則馬上執(zhí)行job咙好,否則執(zhí)行effect.run更新oldValue;如果flushpost褐荷,會將effect.run函數(shù)放到延遲隊(duì)列中延遲執(zhí)行勾效;其他情況執(zhí)行effect.run

最后返回一個(gè)停止watch的函數(shù)叛甫。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末层宫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子其监,更是在濱河造成了極大的恐慌萌腿,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抖苦,死亡現(xiàn)場離奇詭異毁菱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)锌历,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門贮庞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人究西,你說我怎么就攤上這事贸伐。” “怎么了怔揩?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵捉邢,是天一觀的道長。 經(jīng)常有香客問我商膊,道長伏伐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任晕拆,我火速辦了婚禮藐翎,結(jié)果婚禮上材蹬,老公的妹妹穿的比我還像新娘。我一直安慰自己吝镣,他們只是感情好堤器,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著末贾,像睡著了一般闸溃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拱撵,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天辉川,我揣著相機(jī)與錄音,去河邊找鬼拴测。 笑死乓旗,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的集索。 我是一名探鬼主播屿愚,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼务荆!你這毒婦竟也來了妆距?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蛹含,失蹤者是張志新(化名)和其女友劉穎毅厚,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浦箱,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吸耿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了酷窥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咽安。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蓬推,靈堂內(nèi)的尸體忽然破棺而出妆棒,到底是詐尸還是另有隱情,我是刑警寧澤沸伏,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布糕珊,位于F島的核電站,受9級特大地震影響毅糟,放射性物質(zhì)發(fā)生泄漏红选。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一姆另、第九天 我趴在偏房一處隱蔽的房頂上張望喇肋。 院中可真熱鬧坟乾,春花似錦、人聲如沸蝶防。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽间学。三九已至殷费,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間菱鸥,已是汗流浹背宗兼。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工躏鱼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留氮采,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓染苛,卻偏偏與公主長得像鹊漠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子茶行,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容