【vue3源码学习】watch 源码解析

396 阅读3分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」

watch 在 vue 中使用

watch 本质就是一个响应式数据,当数据发生变化的时候,执行相应的回调函数。

watch(obj, ()=>{
    console.log(123)
})
// 当监测到数据变化时,会重新执行 watch 的回调函数
  • watch 需要监听特定的数据源,在第一次加载时不运行函数,只有当监听的数据源变化时才会运行

为了根据响应式状态自动应用和重新应用副作用,我们还可以使用 watchEffect 函数。它会立即执行传入的一个函数,同时响应式追踪依赖,并在依赖更新时重新运行该函数。

  • 可以看出 watchEffect 函数不需要传入一个数据源,只需传入一个函数,在函数中使用到的响应式数据,vue 都会追踪依赖,当数据改变时,watchEffect 会再次运行。
  • 与 watch 不同的是,watchEffect 在页面第一次加载的时候就会执行

源码分析

在 Vue2中watchoption 写法中一个很常用的选项,使用它可以非常方便的监听一个数据源的变化,而在 Vue3 中watch 独立成了一个 响应式api。

watchEffect

watchEffect 的许多行为与 watch 一致,我们来看他的实现:

// 首先来看参数类型
export type WatchEffect = (onInvalidate: InvalidateCbRegistrator) => void

export interface WatchOptionsBase extends DebuggerOptions {
  flush?: 'pre' | 'post' | 'sync'
}

export type WatchStopHandle = () => void

export function watchEffect(
  effect: WatchEffect,  // 接收函数类型的变量,并且在这个函数中会传入 onInvalidate 参数,用以清除副作用
  options?: WatchOptionsBase // 在这个对象中有三个属性,你可以修改 flush 来改变副作用的刷新时机,默认为 pre,当修改为 post 时,就可以在组件更新后触发这个副作用侦听器,改同 sync 会强制同步触发。而 onTrack 和 onTrigger 选项可以用于调试侦听器的行为,并且两个参数只能在开发模式下工作。
): WatchStopHandle {
  return doWatch(effect, null, options)
}

watch

当我们调用 watch 的时候,实际上是调用的 runtime-core/apiWatch.ts 中的 watch 方法:

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 接收 3 个参数,source 侦听的数据源,cb 回调函数,options 侦听选项。

(1)source 参数

export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
type MultiWatchSources = (WatchSource<unknown> | object)[]

从两个类型定义看出,数据源支持传入单个的 RefComputed 响应式对象,或者传入一个返回相同泛型类型的函数,以及 source 支持传入数组,以便能同时监听多个数据源。

(2)cb 参数

在这个最通用的声明中,cb 的类型是 any,但 cb 这个回调函数也有类型:

export type WatchCallback<V = any, OV = any> = (
  value: V,
  oldValue: OV,
  onInvalidate: InvalidateCbRegistrator
) => any

在回调函数中,会提供最新的 value、旧 value,以及 onInvalidate 函数用以清除副作用。

(3)options

export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
  immediate?: Immediate
  deep?: boolean
}

可以看到 options 的类型 WatchOptions 继承了 WatchOptionsBase。

分析完参数后,可以看到函数体内的逻辑与 watchEffect 几乎一致,但是多了在开发环境下检测回调函数是否是函数类型,如果回调函数不是函数,就会报警。执行 doWatch 时的传参与 watchEffect 相比,多了第二个参数回调函数。