大家好,我是剑大瑞。

这边文章次要剖析Vue3中watch API的应用 & 实现原理,心愿文中的内容对你有所帮忙。

如果有谬误之处还望斧正。我会非常感谢你的。

如果没有对Vue3的响应式原理不相熟,倡议先浏览第一篇Vue3 reactivity 源码解析。

computed API

在上篇文章中咱们剖析了computed的原理。在runtime core中,有对computed做了一层解决,次要是记录以后实例的computed Effectinstance.effects。不便组件卸载的时候,清空依赖。

runtime core中的代码较为简单,咱们一起简略看下。

  • 记录组件创立阶段的 computed Effect instance.effects,不便组件卸载的时候,移除以后实例的 computed effect
  • 咱们应用的computed API就是通过runtime core解决过的computed
import {  computed as _computed,} from '@vue/reactivity'// 来自component.ts文件function recordInstanceBoundEffect(effect,instance = currentInstance) {  if (instance) {    ;(instance.effects || (instance.effects = [])).push(effect)  }}function computed(getterOrOptions) {  const c = _computed(getterOrOptions)  // c.effect  recordInstanceBoundEffect(c.effect)  return c}

watch API

Vue3中新增了一个响应式函数:watchEffect。在剖析watch & watchEffect之前,咱们一起先回顾下这两个API如何应用。

回顾

watchwatchEffect都属于Vue中的响应式API。

留神:一提到响应式,大家就应该想到:getter & tracksetter & trigger

watchEffect

  • watchEffect能够依据响应数据状态的变动,主动或者从新执行传入的副作用函数。
  • 他承受一个回调函数,并在创立的时候立刻执行,同时对齐进行响应式依赖追踪。
  • 即建设以后传入的回调函数与所有相干effect的依赖关系。
  • 并在依赖变动的时候从新运行该回调函数。
  • 并会返回一个stop函数,用来进行侦听,即断开以后watchEffect与其所有依赖的effect之间的关系
const count = ref(0)const stop = watchEffect(() => console.log(count.value))// -> logs 0setTimeout(() => {  count.value++  // -> logs 1}, 100)// 进行侦听stop()count.value += 1

当然watchEffect也能够承受异步回调函数作为参数。当回调函数为异步时:

  • watchEffect能够给传入的函数传递一个异步的onInvalidate函数作为入参,用来注册清理watchEffect生效时的回调函数
  • 何时watchEffect会生效:

    • 当手动调用stop函数的时候
    • 当组件卸载的时候
const stop = watchEffect(onInvalidate => {  const token = performAsyncOperation(id.value)  onInvalidate(() => {    // 当调用stop函数时,会执行给onInvalidate传入的回调函数    token.cancel()  })})onUnmounted(() => {    console.log('组件卸载')})

为了进步刷新效率,Vue的响应式零碎会缓存并异步解决所有watchEffect副作用函数,以防止同一个“tick” 中多个状态扭转导致的不必要的反复调用。

对于如何缓存并异步解决,稍后源码中进行解析

配置watchEffectwatchEffect能够承受两个参数,第二个参数对watchEffect进行配置:

  • 默认状况下(flush: 'pre')watchEffect副作用会在所有的组件 update 执行
  • 当设置flush: 'post'时,组件更新后会从新运行watchEffect副作用
  • 当设置flush: 'sync'时,这将强制成果始终同步触发watchEffect副作用
<template>  <div>{{ count }}</div></template><script>export default {  setup() {    const count = ref(0)    // 更新前触发    watchEffect(() => {      console.log(count.value)    }, {        flush: 'pre'      })    // 更新后触发    watchEffect(() => {      console.log(count.value)    }, {        flush: 'post'      })    // 同步触发    watchEffect(() => {      console.log(count.value)    }, {        flush: 'sync'      })    return {      count    }  }}</script>

watch

  • watch等同于组件侦听器property
  • 须要侦听特定的响应式数据源
  • 并在回调喊胡世宗执行副作用
  • 默认状况下是惰性的,只有当侦听的数据源发生变化的时候才会执行回调
侦听单个数据源:
// 侦听一个 getterconst state = reactive({ count: 0 })watch(  () => state.count,  (count, prevCount) => {    /* ... */  })// 间接侦听refconst count = ref(0)watch(count, (count, prevCount) => {  /* ... */})
侦听多个数据源(间接侦听ref):
留神尽管侦听的是多个数据源,然而当多个数据源产生扭转的时候,侦听器仍只会执行一次
setup() {  const firstName = ref('')  const lastName = ref('')  watch([firstName, lastName], (newValues, prevValues) => {    console.log(newValues, prevValues)  })  const changeValues = () => {    firstName.value = 'John'    lastName.value = 'Smith'    // 打印 ["John", "Smith"] ["", ""]  }  return { changeValues }}
侦听响应式对象
  • deep可进行深度侦听
  • immediate可进行立刻侦听
const state = reactive({   id: 1,  attributes: {     name: '',  }})watch(  () => state,  (state, prevState) => {    console.log('not deep', state.attributes.name, prevState.attributes.name)  })// 深度并立刻响应侦听watch(  () => state,  (state, prevState) => {    console.log('deep', state.attributes.name, prevState.attributes.name)  },  { deep: true, immediate: true })state.attributes.name = 'Alex' // 日志: "deep" "Alex" "Alex"

这里须要说下 【副作用】、【依赖】都是咱们上一篇文章中提到的effet

比拟要害的是,咱们这里接触的是Vue源码中的第二个级别的effect,第一个是compute Effect。这次要说的是watch Effect

Ok,到这里咱们根本曾经回顾完这两个响应式API如何应用了,上面咱们联合源码,进行剖析。

剖析

通过回顾局部的内容,根本曾经对watch & watchEffect有了点感觉,既然要剖析源码实现,最好是带着问题来看:

  1. watchEffect是如何进行侦听的?
  2. watchEffect是如何进行函数缓存的?
  3. watchEffect是如何异步进行刷新的?
  4. watch是如何侦听单个或者多个数据源的?
  5. watch是如何进行深度或者立刻侦听响应的?

Vue3中的watch代码中设计的性能比拟多,为了不便了解,咱们拆开来一点一点进行解析

watchEffect是如何进行侦听的?

后面提到watch其实也是一个effect,所谓的侦听就是watch与其余effect之间建设一个依赖关系,当数据发生变化的时候,去遍历执行所有的effect,就会执行watch

在上一篇文章中咱们提到,effect中有个stop函数,用于断开传入effect与之相干的依赖之间的关系。

所谓的进行侦听就是断开watch与所有相干effect的依赖关系。

当创立watch Effect时,会为其保护一个deps属性,用于存储所有的dep。故当咱们创立watch的时候,将以后runner传给stop函数,并返回一个函数,用户调用的时候,就会进行侦听。

上面代码咱们临时省略与进行侦听无关的代码,只需了解能解答问题的局部即可。

// reactive effect.ts 文件export function stop(effect) {  if (effect.active) {    cleanup(effect)    if (effect.options.onStop) {      effect.options.onStop()    }    effect.active = false  }}function cleanup(effect) {  const { deps } = effect  if (deps.length) {    for (let i = 0; i < deps.length; i++) {      deps[i].delete(effect)    }    deps.length = 0  }}// 真正的watch函数function doWatch(  source,  cb,  { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ,  instance ) {  // 定义runner  // watch 级别的effect  // runner执行,即执行getter函数  const runner = effect(getter, {    lazy: true,    onTrack,    onTrigger,    scheduler  })    // 返回一个stop函数  // 用于断开runner与其余依赖之间的关系  // 并将其将从instance.effects中移除  return () => {    stop(runner)    //     if (instance) {      remove(instance.effects!, runner)    }  }}

runner就是effect API创立的watch Effectwatch对数据源进行侦听的时候,就会与其余依赖之间建设关系。即effectdep之间互相耦合的关系。

当须要进行侦听的时候,通过调用doWatch返回的函数就能够断开runner与其余dep的依赖关系。

watch是如何侦听单个或者多个数据源的?

在回顾局部咱们晓得,watch能够进行多种数据响应式数据类型的监听。

watch侦听的数据源发生变化的时候就会执行callback。这就是后面咱们说的响应式。

在应用watch时,doWatch会创立一个 getter函数,用于确定数据源与callback之间的关系。

getter函数用于获取数据源的更新后的值。当getter函数执行的时候,就会触发依赖收集。

所以Vue3是在getter函数中对数据源进行判断侦听的。上面咱们先看下源码的getter局部,在持续剖析。

function doWatch(  source,  cb,  { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ,  instance = currentInstance) {  /**  *  省略局部代码...  */         let getter  let forceTrigger = false  let isMultiSource = false    /* Start: 开始定义getter函数 */  if (isRef(source)) {          // 第一种状况:     // 源是ref类型    getter = () => source.value    forceTrigger = !!source._shallow  } else if (isReactive(source)) {          // 第二种状况:     // 源是响应式对象,响应式对象主动进行深度侦听    getter = () => source    deep = true  } else 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)        }      })  } else if (isFunction(source)) {          // 第四种状况:      // 数据源是函数    if (cb) {      // getter with cb      getter = () =>        callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)    } else {            // 分支状况:        // no cb -> simple effect      // 没有传回调函数的状况      getter = () => {        if (instance && instance.isUnmounted) {          return        }        if (cleanup) {          cleanup()        }        return callWithAsyncErrorHandling(          source,          instance,          ErrorCodes.WATCH_CALLBACK,          [onInvalidate]        )      }    }  } else {    // 数据源可能有问题 进行校验 提醒    getter = NOOP    __DEV__ && warnInvalidSource(source)  }  /* End: 定义getter函数完结 */  /**  *  省略局部代码...  */ }

从下面的代码咱们能够看出,doWatch外部共对数据源进行了四种状况的判断。

  • 并且当数据源是响应式数据类型时,会主动进行深度侦听
  • 当侦听的是多个数据源的时候,会进行递归遍历
  • 当数据源是函数的时候,getter函数最终会调用 callWithAsyncErrorHandling异步执行侦听的函数。
  • deep为ttrue时,会对getter进行递归遍历。

再联合下面剖析进行侦听的代码,能够晓得,当runner函数执行的时候,就是执行getter获取数据源新值的时候。

深度侦听函数:

  • 是一个深度递归遍历函数
  • 通过seen函数判断循环援用的状况
  • 最终返回的是原始类型数据
//  递归遍历获取值,seen用于避免陷入死循环function traverse(value, seen = new Set()) {  if (    !isObject(value) ||    seen.has(value) ||    (value)[ReactiveFlags.SKIP]  ) {    return value  }      seen.add(value)      if (isRef(value)) {    traverse(value.value, seen)  } else if (isArray(value)) {    for (let i = 0; i < value.length; i++) {      traverse(value[i], seen)    }  } else if (isSet(value) || isMap(value)) {    value.forEach((v: any) => {      traverse(v, seen)    })  } else if (isPlainObject(value)) {    for (const key in value) {      traverse((value)[key], seen)    }  }  return value}

watchEffect是如何进行函数缓存 & 异步进行刷新的?

咱们晓得当应用effect函数创立runner的时候,其实是创立了一个watch Effect

这里回顾下,在computed中,Vue3是间接将effect返回的函数,赋给ComputedRefImpl实例的effect属性,并在实例的getter函数中调用this.effect,从而获取 value。

在应用watch的时候,咱们并没有间接或间接应用effect函数返回的watch Effect函数获取新值。也没有必要这么应用。

然而咱们须要在相干依赖发生变化的时候从新执行watch Effect获取新值 & 执行callback。那该如何做?

答案是通过给watch Effect 配置scheduler属性。

当进行响应派发的时候,会触发trigger函数,trigger函数最终会遍历执行所有相干effect

在执行effect的过程中会判断effect.scheduler是否存在,如果存在就会执行scheduler函数。

watch Effect就是在scheduler中做的副作用函数的缓存和异步刷新的。

还是原来的套路,让咱们先看下scheduler源码局部:

// 真正的watch函数function doWatch(  source,  cb,  { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ,  instance = currentInstance) {  /**  *  省略局部代码...  */   // 调度器,有没有想到computed API 创立的时候,在配置项中设置的 scheduler  // 在computed中scheduler次要负责重置 dirty  // 当 watche Effect 侦测的数据源发生变化的时候  // 会进行trigger,遍历执行所有与数据源相干的 effect  // 在遍历的过程中会判断effect.scheduler 是否存在  // 如果存在 则会执行scheduler(任务调度器),这一点与咱们第一篇提到的computed的原理一样  // scheduler执行 其实就是在执行job,job执行就是在执行 runner Effect  // 即 watch Effect  let scheduler  if (flush === 'sync') {    // 同步更新    scheduler = job // 任务调度函数被间接调用  } else if (flush === 'post') {    // 组件更新后    scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)  } else {    // 默认状况下    // 通过queuePreFlushCb缓存job,并异步更新副作用函数    scheduler = () => {      if (!instance || instance.isMounted) {        queuePreFlushCb(job)      } else {        // 应用 'pre' 选项,第一次调用必须在组件装置之前产生,以便同步调用。        job()      }    }  }  // 创立watch Effect  const runner = effect(getter, {    lazy: true,    onTrack,    onTrigger,    scheduler  })  /**  *  省略局部代码...  */ }

通过下面的代码,咱们简略剖析scheduler的创立过程:

  • 次要与创立watch时配置的flush无关
  • 在默认下状况下scheduler外部通过queuePreFlushCb将job缓存在待执行队列中,并通过Promise.resolve异步更新队列从而防止不必要的反复调用
  • 通过Promise创立微工作。在update之前执行所有的副作用函数,等于是进步了副作用函数的优先级

这里咱们先晓得watchEffect是通过queuePreFlushCb做到的副作用函数缓存 & 异步批量更新。在后续的文章中会剖析scheduler.ts局部的内容。到时候就会明确其作用。

另:基础薄弱的同学,倡议相熟下浏览器的宏工作与微工作相干常识。

在下面的代码中,能够晓得scheduler次要的职责就是依据状况对job进行解决,那job是什么?

job 就是异步队列中的一个个工作。次要负责:

  • 通过判断callback,对watchwatchEffect进行判断
  • 通过执行runner获取新值
  • 通过callWithAsyncErrorHandling对callback函数进行异步解决,并将新旧值传给callback,这也是咱们为什么能够在watch中拿到侦听数据源,变动前后value的起因。

上面一起看下job局部的代码实现:

// Simple effect.// watchEffect 的创立形式function watchEffect(  effect,  options) {  return doWatch(effect, null, options)}// watch 的创立形式function watch (source, cb, options) {  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, cb, options)}// 真正的watch函数function doWatch(  source,  cb,  { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ,  instance = currentInstance) {  /**  *  省略局部代码...  */         let cleanup   // 定义生效时须要传参的函数  let onInvalidate = (fn) => {    // 用于执行用户传进来的fn函数    cleanup = runner.options.onStop = () => {      callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)    }  }  let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE  // 定义工作队列中的工作  // 用于执行runner函数  // 执行的过程会进行track & trigger  const job = () => {    if (!runner.active) {      return    }    if (cb) {      // watch(source, cb)      // runner执行就是在执行getter函数,获取newValue      const newValue = runner()      if (        deep ||        forceTrigger ||        (isMultiSource          ? (newValue).some((v, i) =>              hasChanged(v, (oldValue)[i])            )          : hasChanged(newValue, oldValue)) ||        (__COMPAT__ &&          isArray(newValue) &&          isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance))      ) {                  if (cleanup) {          cleanup()        }                  // 执行回调函数        // 因为咱们在传入的cb中很有可能读取或者更改响应式数据        // 因而可能会进行 track || trigger        // 将newValue & oldValue传给cb        callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [          newValue,           oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,          onInvalidate        ])        // 将新值赋值给旧值        oldValue = newValue      }    } else {              // watchEffect(effect)      // watchEffect      // watchEffect API的解决形式,间接执行runner      runner()    }  }  // 将job标记为一个能够侦测的回调函数,以便调度器晓得他能够主动进行响应触发(trigger)  job.allowRecurse = !!cb  /**  *  省略局部代码...  */ }

通过下面代码,能够晓得:

  • 对于watchEffect,执行job,就是在间接执行runner函数
  • 对于watch,首先须要通过runner获取新的value,并将新旧值传给callback函数。

watch是如何进行深度或者立刻侦听响应的?

这里就很简略了,间接上代码:

深度侦听就是去遍历递归原来的getter函数

立刻侦听即间接执行job函数,触发runner,并执行callback

// 真正的watch函数function doWatch(  source,  cb,  { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ,  instance = currentInstance) {  /**  *  省略局部代码...  */   // 深度侦听  if (cb && deep) {    // 深度侦听,则递归遍历getter函数返回的值    const baseGetter = getter    getter = () => traverse(baseGetter())  } /**  *  省略局部代码...  */   // initial run  if (cb) {     // 立刻响应侦听    if (immediate) {      // 立刻执行      // 即进行track & trigger      job()    } else {      oldValue = runner()    }  } else if (flush === 'post') {    queuePostRenderEffect(runner, instance && instance.suspense)  } else {    runner()  }  /**  *  省略局部代码...  */ }

如何做Vue2的兼容解决

Vue3在doWatch函数中,还做了一层Vue2的兼容解决,次要是通过对getter函数进行了一层重载,并对getter函数返回的value进行了深度递归遍历。

// 真正的watch函数function doWatch(  source,  cb,  { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ,  instance = currentInstance) {    /**  *  省略局部代码...  */    // 2.x array mutation watch compat  // Vue2做兼容解决  if (__COMPAT__ && cb && !deep) {    const baseGetter = getter    getter = () => {      const val = baseGetter()      if (        isArray(val) &&        checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)      ) {        traverse(val)      }      return val    }  }   /**  *  省略局部代码...  */ }

总结

  • 通过下面的剖析,咱们当初把握了两个源码级别的Effect,一个是computed Effect,一个是watch Effect
  • watch能对数据源进行响应式侦听。次要是通过将数据源转化为getter函数。
  • 并通过effect建设watch Effect与相干依赖之间的关系。
  • 当数据源发生变化的时候,会触发Trigger,进行响应派发,遍历执行所有相干的effect
  • effect.scheduler存在时,就会执行scheduler函数,而watch外部通过scheduler,对job工作进行了缓存,并放在一个待执行队列中,在update前,会通过promise异步执行job工作。
  • job执行,就会获取数据源变动后的值,并将新旧value传给用户创立watch时的回调函数。实现侦听工作。
不要遗记,在job中获取新值也会触发Track工作。

最初让咱们看下残缺的watch相干局部的代码:

//  watcheffect.export function watchEffect(  effect: WatchEffect,  options?: WatchOptionsBase): WatchStopHandle {  return doWatch(effect, null, options)}//  进行重载,侦听多个数据源 & cbexport function watch<  T extends MultiWatchSources,  Immediate extends Readonly<boolean> = false>(  sources: [...T],  cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,  options?: WatchOptions<Immediate>): WatchStopHandle//  重载:侦听多个数据源,并且数据源是只读的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//  重载:简略watch Effect & cbexport function watch<T, Immediate extends Readonly<boolean> = false>(  source: WatchSource<T>,  cb: WatchCallback<T, Immediate extends true ? (T | undefined) : T>,  options?: WatchOptions<Immediate>): WatchStopHandle//  重载:侦听响应式对象 & cbexport 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//  执行创立 watchexport function watch<T = any, Immediate extends Readonly<boolean> = false>(  source: T | WatchSource<T>,  cb: any,  options?: WatchOptions<Immediate>): WatchStopHandle {  if (__DEV__ && !isFunction(cb)) {     // 省略...  }  // 返回的是一个stop函数  return doWatch(source as any, cb, options)}// 真正的watch函数function doWatch(  source: WatchSource | WatchSource[] | WatchEffect | object,  cb: WatchCallback | null,  { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ,  instance = currentInstance): WatchStopHandle {  //  dev环境下判断 immediate, deep  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.`    )  }  let getter: () => any  let forceTrigger = false  let isMultiSource = false    /* Start: 开始定义getter函数 */  if (isRef(source)) {    // 源是ref类型    getter = () => source.value    forceTrigger = !!source._shallow  } else if (isReactive(source)) {          //  源是响应式对象,主动进行深度侦听    getter = () => source    deep = true  } else 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)        }      })  } else if (isFunction(source)) {    //  数据源是函数    if (cb) {      // getter with cb      getter = () =>        callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)    } else {      // no cb -> simple effect      // 没有传回调函数的状况,watchEffect      getter = () => {        if (instance && instance.isUnmounted) {          return        }        if (cleanup) {          cleanup()        }        return callWithAsyncErrorHandling(          source,          instance,          ErrorCodes.WATCH_CALLBACK,          [onInvalidate]        )      }    }  } else {    getter = NOOP    __DEV__ && warnInvalidSource(source)  }  /* End: 定义getter函数完结 */  //  Vue2做兼容解决  if (__COMPAT__ && cb && !deep) {    const baseGetter = getter    getter = () => {      const val = baseGetter()      if (        isArray(val) &&        checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)      ) {        traverse(val)      }      return val    }  }  if (cb && deep) {    // 深度侦听,则递归遍历getter函数返回的值    const baseGetter = getter    getter = () => traverse(baseGetter())  }  let cleanup: () => void  //  定义生效时须要传参的函数  let onInvalidate: InvalidateCbRegistrator = (fn: () => void) => {    cleanup = runner.options.onStop = () => {      callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)    }  }  //  服务端渲染的状况下,不用创立一个真正的effect, onInvalidate 应该为一个空对象,  // 触发 immediate 为true  if (__NODE_JS__ && isInSSRComponentSetup) {    // we will also not call the invalidate callback (+ runner is not set up)    onInvalidate = NOOP    if (!cb) {      getter()    } else if (immediate) {      callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [        getter(),        undefined,        onInvalidate      ])    }    return NOOP  }  let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE  //  定义工作队列中的工作,用于执行runner函数,执行的过程会进行track & trigger  const job: SchedulerJob = () => {    if (!runner.active) {      return    }    if (cb) {      // watch(source, cb)      // runner执行就是在执行getter函数,获取newValue      const newValue = runner()      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))      ) {        // watch API的解决形式        // cleanup before running cb again        if (cleanup) {          cleanup()        }        // 执行回调函数        // 因为咱们在传入的cb中很有可能读取或者更改响应式数据        // 因而可能会进行 track || trigger        // 将newValue & oldValue传给cb        callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [          newValue,           oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,          onInvalidate        ])        // 将新值赋值给旧值        oldValue = newValue      }    } else {      // watchEffect      // watchEffect API的解决形式,间接执行runner      runner()    }  }  //  将job标记为一个能够侦测的回调函数,以便调度器晓得他能够主动进行响应触发(trigger)  job.allowRecurse = !!cb        //   // 调度器,有没有想到computed API 创立的时候,在配置项中设置的 scheduler  // 在computed中scheduler次要负责重置 dirty  // 当 watche Effect 侦测的数据源发生变化的时候  // 会进行trigger,遍历执行所有与数据源相干的 effect  // 在遍历的过程中会判断effect.scheduler 是否存在  // 如果存在 则会执行scheduler(任务调度器),这一点与咱们第一篇提到的computed的原理一样  // scheduler执行 其实就是在执行job,job执行就是在执行 runner Effect  // 即watch Effect  let scheduler: ReactiveEffectOptions['scheduler']  if (flush === 'sync') {    // 同步更新    scheduler = job as any // 任务调度函数被间接调用  } else if (flush === 'post') {    // 组件更新后    scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)  } else {    // default: 'pre'    // 默认状况下    scheduler = () => {      if (!instance || instance.isMounted) {        queuePreFlushCb(job)      } else {        // 应用 'pre' 选项,第一次调用必须在组件装置之前产生,以便同步调用。        job()      }    }  }  //  定义runner, watch 级别的effect,runner执行,即执行getter函数  const runner = effect(getter, {    lazy: true,    onTrack,    onTrigger,    scheduler  })    // 将watch effect 存至instance.effects,当组件卸载的时候会清空以后runner与依赖之间的关系  recordInstanceBoundEffect(runner, instance)  // initial run  if (cb) {    if (immediate) {      // 立刻执行,即进行track & trigger      job()    } else {      oldValue = runner()    }  } else if (flush === 'post') {    queuePostRenderEffect(runner, instance && instance.suspense)  } else {    runner()  }  // 返回一个stop函数,用于断开runner与其余依赖之间的关系,并将其将从instance.effects中移除  return () => {    stop(runner)    //     if (instance) {      remove(instance.effects!, runner)    }  }}//  this.$watch,组件实例上的watch APIexport function instanceWatch(  this: ComponentInternalInstance,  source: string | Function,  value: WatchCallback | ObjectWatchOptionItem,  options?: WatchOptions): WatchStopHandle {  const publicThis = this.proxy as any  //  定义getter函数  const getter = isString(source)    ? source.includes('.')      ? createPathGetter(publicThis, source)      : () => publicThis[source]    : source.bind(publicThis, publicThis)  let cb  if (isFunction(value)) {    cb = value  } else {    cb = value.handler as Function    options = value  }  return doWatch(getter, cb.bind(publicThis), options, this)}//  获取侦听门路export function createPathGetter(ctx: any, path: string) {  const segments = path.split('.')  return () => {    let cur = ctx    for (let i = 0; i < segments.length && cur; i++) {      cur = cur[segments[i]]    }    return cur  }}//  递归遍历获取值,seen用于避免陷入死循环function traverse(value: unknown, seen: Set<unknown> = new Set()) {  if (    !isObject(value) ||    seen.has(value) ||    (value as any)[ReactiveFlags.SKIP]  ) {    return value  }  seen.add(value)  if (isRef(value)) {    traverse(value.value, seen)  } else if (isArray(value)) {    for (let i = 0; i < value.length; i++) {      traverse(value[i], seen)    }  } else if (isSet(value) || isMap(value)) {    value.forEach((v: any) => {      traverse(v, seen)    })  } else if (isPlainObject(value)) {    for (const key in value) {      traverse((value as any)[key], seen)    }  }  return value}

如果文章中有谬误之处,还望大佬们批评指正。

如果喜爱我的文章,能够关注 + 点赞。

如果须要继续理解Vue3源码剖析系列,可关注我公共号【coder狂想曲】。在这里咱们一起精进!年年double!

感激浏览。

参考:

  • Vue官网
  • Vue-next