简化总结

Vue 3 的 watch 是响应式系统的高级功能之一,它用于侦听响应式数据的变化并执行回调。
其底层是基于 ReactiveEffect 的依赖追踪机制实现的。

源码位于 @vue/reactivity 包中。


一、导入依赖

import {
  EMPTY_OBJ,
  NOOP,
  hasChanged,
  isArray,
  isFunction,
  isMap,
  isObject,
  isPlainObject,
  isSet,
  remove,
} from '@vue/shared'
import { warn } from './warning'
import type { ComputedRef } from './computed'
import { ReactiveFlags } from './constants'
import {
  type DebuggerOptions,
  EffectFlags,
  type EffectScheduler,
  ReactiveEffect,
  pauseTracking,
  resetTracking,
} from './effect'
import { isReactive, isShallow } from './reactive'
import { type Ref, isRef } from './ref'
import { getCurrentScope } from './effectScope'
  • 判断数据类型(isRef, isReactive, isFunction, isArray 等)
  • 响应式核心类(ReactiveEffect
  • 工具函数(hasChangedremove
  • 生命周期追踪与暂停追踪(pauseTracking / resetTracking

二、错误码定义

export enum WatchErrorCodes {
  WATCH_GETTER = 2,
  WATCH_CALLBACK,
  WATCH_CLEANUP,
}
错误码含义
2取值(getter)时出错
3回调函数执行出错
4清理函数执行出错

三、类型定义

export type WatchEffect = (onCleanup: OnCleanup) => void
export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
export type WatchCallback<V = any, OV = any> = (value: V, oldValue: OV, onCleanup: OnCleanup) => any
export type OnCleanup = (cleanupFn: () => void) => void
  • WatchEffect:传入一个函数,自动执行并响应式追踪(即 watchEffect)。
  • WatchSource:侦听的源,可以是 ref、computed 或函数。
  • WatchCallback:当源变化时的回调函数。

四、watch 选项接口

export interface WatchOptions<Immediate = boolean> extends DebuggerOptions {
  immediate?: Immediate
  deep?: boolean | number
  once?: boolean
  scheduler?: WatchScheduler
  onWarn?: (msg: string, ...args: any[]) => void
  augmentJob?: (job: (...args: any[]) => void) => void
  call?: (
    fn: Function | Function[],
    type: WatchErrorCodes,
    args?: unknown[],
  ) => void
}
  • immediate: 是否立即执行一次回调。
  • deep: 是否深度侦听。
  • once: 是否只触发一次。
  • scheduler: 自定义调度函数(可实现 flush 机制)。
  • augmentJob: 用于增强 job(Vue 内部使用)。
  • call: 自定义调用函数(可注入错误处理逻辑)。

五、全局变量与工具函数

const cleanupMap: WeakMap<ReactiveEffect, (() => void)[]> = new WeakMap()
let activeWatcher: ReactiveEffect | undefined = undefined
  • 每个 ReactiveEffect 对应的清理函数。
  • 当前正在执行的 watcher(用于 onWatcherCleanup)。

获取当前 watcher

export function getCurrentWatcher(): ReactiveEffect<any> | undefined {
  return activeWatcher
}

注册清理函数

export function onWatcherCleanup(
  cleanupFn: () => void,
  failSilently = false,
  owner: ReactiveEffect | undefined = activeWatcher,
): void {
  if (owner) {
    let cleanups = cleanupMap.get(owner)
    if (!cleanups) cleanupMap.set(owner, (cleanups = []))
    cleanups.push(cleanupFn)
  } else if (__DEV__ && !failSilently) {
    warn(
      `onWatcherCleanup() was called when there was no active watcher`
    )
  }
}

六、watch 核心函数实现

export function watch(
  source: WatchSource | WatchSource[] | WatchEffect | object,
  cb?: WatchCallback | null,
  options: WatchOptions = EMPTY_OBJ,
): WatchHandle {
  const { immediate, deep, once, scheduler, augmentJob, call } = options

️ 参数说明

  • source: 侦听的目标(可以是 ref、reactive、函数、数组)。
  • cb: 回调函数,如果不存在,则视为 watchEffect
  • options: 配置项。

1. 校验与辅助函数

const warnInvalidSource = (s: unknown) => {
  ;(options.onWarn || 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.`,
  )
}

2. 定义 reactiveGetter

const reactiveGetter = (source: object) => {
  if (deep) return source
  if (isShallow(source) || deep === false || deep === 0)
    return traverse(source, 1)
  return traverse(source)
}

3. 分析 source 类型并构造 getter

这一段是 watch 的核心逻辑之一:根据不同类型的 source,创建合适的 getter。

if (isRef(source)) {
  getter = () => source.value
  forceTrigger = isShallow(source)
} else if (isReactive(source)) {
  getter = () => reactiveGetter(source)
  forceTrigger = true
} else if (isArray(source)) {
  isMultiSource = true
  ...
} else if (isFunction(source)) {
  ...
} else {
  getter = NOOP
  __DEV__ && warnInvalidSource(source)
}
  • isRef: 直接访问 .value
  • isReactive: 通过 traverse 收集依赖。
  • isArray: 多源 watch。
  • isFunction: 区分 watch(source, cb)watchEffect(source)
  • 其他情况警告无效来源。

4. 深度 watch 包装

if (cb && deep) {
  const baseGetter = getter
  const depth = deep === true ? Infinity : deep
  getter = () => traverse(baseGetter(), depth)
}

5. 创建 ReactiveEffect

effect = new ReactiveEffect(getter)

ReactiveEffect 是 Vue 响应式系统的核心执行单元,负责依赖收集与更新调度。


6. 定义 job(调度任务)

const job = (immediateFirstRun?: boolean) => {
  if (!effect.dirty && !immediateFirstRun) return
  if (cb) {
    const newValue = effect.run()
    if (deep || forceTrigger || hasChanged(newValue, oldValue)) {
      if (cleanup) cleanup()
      const currentWatcher = activeWatcher
      activeWatcher = effect
      try {
        cb(newValue, oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue, boundCleanup)
        oldValue = newValue
      } finally {
        activeWatcher = currentWatcher
      }
    }
  } else {
    // watchEffect
    effect.run()
  }
}

7. 调度与初始化执行

effect.scheduler = scheduler
  ? () => scheduler(job, false)
  : (job as EffectScheduler)

初始化:

if (cb) {
  if (immediate) job(true)
  else oldValue = effect.run()
} else if (scheduler) {
  scheduler(job.bind(null, true), true)
} else {
  effect.run()
}

8. 返回控制句柄

watchHandle.pause = effect.pause.bind(effect)
watchHandle.resume = effect.resume.bind(effect)
watchHandle.stop = watchHandle
return watchHandle
  • pause(): 暂停依赖收集。
  • resume(): 恢复依赖收集。
  • stop(): 停止监听。

七、traverse 函数(深度遍历)

export function traverse(
  value: unknown,
  depth: number = Infinity,
  seen?: Map<unknown, number>,
): unknown {
  if (depth <= 0 || !isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
    return value
  }

  seen = seen || new Map()
  if ((seen.get(value) || 0) >= depth) {
    return value
  }
  seen.set(value, depth)
  depth--
  
  if (isRef(value)) {
    traverse(value.value, depth, seen)
  } else if (isArray(value)) {
    for (let i = 0; i < value.length; i++) {
      traverse(value[i], depth, seen)
    }
  } else if (isSet(value) || isMap(value)) {
    value.forEach(v => traverse(v, depth, seen))
  } else if (isPlainObject(value)) {
    for (const key in value) traverse(value[key], depth, seen)
    for (const key of Object.getOwnPropertySymbols(value)) {
      if (Object.prototype.propertyIsEnumerable.call(value, key)) {
        traverse(value[key as any], depth, seen)
      }
    }
  }
  return value
}

八、总结

watch 的核心思想:

  1. 根据 source 类型创建 getter。
  2. 使用 ReactiveEffect 追踪依赖。
  3. 当依赖变化时执行 job,触发回调。
  4. 支持 immediatedeeponce 等灵活配置。
  5. 通过 traverse 实现深层依赖收集。
  6. 提供 onWatcherCleanup 实现副作用清理机制。

️ watch 与 watchEffect 的区别:

特性watchwatchEffect
参数源 + 回调仅副作用函数
取值方式比较新旧值自动执行函数内部依赖
是否传参需要指定 source自动收集依赖
使用场景精确侦听某个数据变化响应式副作用逻辑

阅读建议

若要深入理解 watch

  1. 先阅读 ReactiveEffect 的实现;
  2. 再研究 track / trigger
  3. 理解 traverse 如何触发深度依赖;
  4. 最后再看 scheduler 如何与 flush 配合。

本文内容由人工智能生成,仅供学习与参考使用,请在实际应用中结合自身情况进行判断。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]