深入理解 Vue3 Watch

574 阅读4分钟

1、使用

watch 函数有三个参数依次是:需要监听的响应式对象、回调函数、配置对象

其中,配置对象 immediate 为初始化后立即执行回调函数,deep 深度监听复杂数据类型, flush 为回调方法执行时机

import { watch, ref } from 'vue'
const info = ref({
  name: 'ayuan'
})
watch(() => info.value, () => {
  // 监听做些什么...
}, {
  immediate: true, // 立即执行
  deep: true // 深度监听
})

2、源码解析

watch 底层调用的是 doWatch 方法,将 Watch 参数直接进行透传

// 位置在 /packages/runtime-core/src/apiWatch.ts 第 157 行
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)
}

doWatch 先是对 source 进行判断,是否是Ref、Reactive、数组、函数这四个对象,不是就警告,

// 位置在 /packages/runtime-core/src/apiWatch.ts 第 192 行
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.`
  )
}

const instance =
  getCurrentScope() === currentInstance?.scope ? currentInstance : null
// const instance = currentInstance
let getter: () => any
let forceTrigger = false
let isMultiSource = false

if (isRef(source)) {
  getter = () => source.value
  forceTrigger = isShallow(source)
} else if (isReactive(source)) {
  getter = () => source
  deep = true
} else if (isArray(source)) {
  isMultiSource = true
  forceTrigger = source.some(s => isReactive(s) || isShallow(s))
  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,
        [onCleanup]
      )
    }
  }
} else {
  getter = NOOP
  __DEV__ && warnInvalidSource(source)
}

接下来处理 deep,如果为 true, 进行深度监听,在新的getter函数中递归getter的返回结果

// 位置在 /packages/runtime-core/src/apiWatch.ts 第 271 行
if (cb && deep) {
  const baseGetter = getter
  getter = () => traverse(baseGetter())
}

// 位置在 /packages/runtime-core/src/apiWatch.ts 第 442 行
export function traverse(value: unknown, seen?: Set<unknown>) {
  if (!isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
    return value
  }
  seen = seen || new Set()
  if (seen.has(value)) {
    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
}

初始化 cleanup,清除副作用函数,其中 callWithErrorHandling,处理用户自定义回调函数异常

// 位置在 /packages/runtime-core/src/apiWatch.ts 第 276 行
let cleanup: () => void
let onCleanup: OnCleanup = (fn: () => void) => {
  cleanup = effect.onStop = () => {
    callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
  }
}

// 处理用户自定义回调函数异常
// 位置在 /packages/runtime-core/src/errorHandling.ts 第 63 行
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
}

SSR清除副作用函数逻辑处理

// 位置在 /packages/runtime-core/src/apiWatch.ts 第 285 行
let ssrCleanup: (() => void)[] | undefined
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
    ])
  }
  if (flush === 'sync') {
    const ctx = useSSRContext() as SSRContext
    ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = [])
  } else {
    return NOOP
  }
}

之后是初始化 job 函数,其本意就是执行回调函数

// 位置在 /packages/runtime-core/src/apiWatch.ts 第 306 行

let oldValue: any = isMultiSource
  ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
  : INITIAL_WATCHER_VALUE

const job: SchedulerJob = () => {
  if (!effect.active) {
    return
  }
  if (cb) {
    // watch(source, 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))
    ) {
      // cleanup before running cb again
      if (cleanup) {
        cleanup()
      }
      callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
        newValue,
        // pass undefined as the old value when it's changed for the first time
        oldValue === INITIAL_WATCHER_VALUE
          ? undefined
          : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
          ? []
          : oldValue,
        onCleanup
      ])
      oldValue = newValue
    }
  } else {
    // watchEffect
    effect.run()
  }
}

生成调度器 scheduler ,而此时也将定义回调函数的执行时机,sync 同步执行,post 组件挂载后执行,pre 组件挂载前执行

// 位置在 /packages/runtime-core/src/apiWatch.ts 第 354 行
let scheduler: EffectScheduler
if (flush === 'sync') {
  scheduler = job as any // the scheduler function gets called directly
} else if (flush === 'post') {
  scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
} else {
  // default: 'pre'
  job.pre = true
  if (instance) job.id = instance.uid
  scheduler = () => queueJob(job)
}

添加响应式对象返回副作用方法,如果是开发环境下还可以添加 onTrackonTrigger

// 位置在 /packages/runtime-core/src/apiWatch.ts 第 366 行
const effect = new ReactiveEffect(getter, scheduler)

if (__DEV__) {
  effect.onTrack = onTrack
  effect.onTrigger = onTrigger
}

处理 immediatetrue,立即执行 job 方法

// 位置在 /packages/runtime-core/src/apiWatch.ts 第 374 行
if (cb) {
  if (immediate) {
    job()
  } else {
    oldValue = effect.run()
  }
} else if (flush === 'post') {
  queuePostRenderEffect(
    effect.run.bind(effect),
    instance && instance.suspense
  )
} else {
  effect.run()
}

最后返回 unwatch 停止监听方法

// 位置在 /packages/runtime-core/src/apiWatch.ts 第 389 行
const unwatch = () => {
  effect.stop()
  if (instance && instance.scope) {
    remove(instance.scope.effects!, effect)
  }
}

if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)
return unwatch

3、总结

Watch 先是对监听对象 source 处理成为 getter,之后便初始化 job 根据 flush 生成不同时机运行的调度器 scheduler,将 getterscheduler 作为 ReactiveEffect 对象生成副作用 effect,最后返回停止监听方法 unwatch

感谢阅读