🧩 Vue 3 watch 源码详解(含完整注释)

简化总结

Vue 3 的 watch 是响应式系统的高级功能之一,它用于侦听响应式数据的变化 并执行回调。

其底层是基于 ReactiveEffect 的依赖追踪机制实现的。

源码位于 @vue/reactivity 包中。


一、导入依赖

python 复制代码
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

二、错误码定义

arduino 复制代码
export enum WatchErrorCodes {
  WATCH_GETTER = 2,
  WATCH_CALLBACK,
  WATCH_CLEANUP,
}

这些错误码来自 Vue 的错误处理系统,用于标识在 watch 执行过程中出错的阶段。

错误码 含义
2 取值(getter)时出错
3 回调函数执行出错
4 清理函数执行出错

三、类型定义

typescript 复制代码
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

这些类型定义帮助明确 watch 的几种使用方式:

  • WatchEffect:传入一个函数,自动执行并响应式追踪(即 watchEffect)。
  • WatchSource:侦听的源,可以是 ref、computed 或函数。
  • WatchCallback:当源变化时的回调函数。

四、watch 选项接口

typescript 复制代码
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: 自定义调用函数(可注入错误处理逻辑)。

五、全局变量与工具函数

javascript 复制代码
const cleanupMap: WeakMap<ReactiveEffect, (() => void)[]> = new WeakMap()
let activeWatcher: ReactiveEffect | undefined = undefined

用于管理:

  • 每个 ReactiveEffect 对应的清理函数。
  • 当前正在执行的 watcher(用于 onWatcherCleanup)。

获取当前 watcher

javascript 复制代码
export function getCurrentWatcher(): ReactiveEffect<any> | undefined {
  return activeWatcher
}

注册清理函数

javascript 复制代码
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`
    )
  }
}

watchEffect 重新执行前,会调用上一次注册的 cleanupFn 清理副作用。


六、watch 核心函数实现

javascript 复制代码
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. 校验与辅助函数

sql 复制代码
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.`,
  )
}

用于在开发环境下提示非法的 watch 源。


2. 定义 reactiveGetter

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

根据 deep 参数决定是否对 reactive 对象做深度遍历(触发依赖收集)。


3. 分析 source 类型并构造 getter

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

ini 复制代码
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 包装

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

如果设置了 deep: true,则对 getter 的结果进行递归遍历,以深度收集依赖。


5. 创建 ReactiveEffect

ini 复制代码
effect = new ReactiveEffect(getter)

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


6. 定义 job(调度任务)

ini 复制代码
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()
  }
}

job 是在依赖变化后执行的任务。

功能包括:

  • 执行 getter 获取新值
  • 比较新旧值是否变化
  • 调用清理函数
  • 执行用户回调

7. 调度与初始化执行

javascript 复制代码
effect.scheduler = scheduler
  ? () => scheduler(job, false)
  : (job as EffectScheduler)

支持自定义调度器,用于实现不同的 flush 策略(例如 postsync)。

初始化:

scss 复制代码
if (cb) {
  if (immediate) job(true)
  else oldValue = effect.run()
} else if (scheduler) {
  scheduler(job.bind(null, true), true)
} else {
  effect.run()
}

8. 返回控制句柄

ini 复制代码
watchHandle.pause = effect.pause.bind(effect)
watchHandle.resume = effect.resume.bind(effect)
watchHandle.stop = watchHandle
return watchHandle

返回一个可控制的句柄对象,包含:

  • pause(): 暂停依赖收集。
  • resume(): 恢复依赖收集。
  • stop(): 停止监听。

七、traverse 函数(深度遍历)

scss 复制代码
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
}

作用:递归访问对象的所有属性,从而触发依赖收集(用于 deep watch)。


八、总结

watch 的核心思想:

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

⚙️ watch 与 watchEffect 的区别:

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

📘 阅读建议

若要深入理解 watch

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

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

相关推荐
大前端helloworld3 小时前
前端梳理体系从常问问题去完善-网络篇
前端·面试
excel3 小时前
🌿 一文看懂 Vue 3 的 watch 源码:从原理到流程
前端
繁依Fanyi4 小时前
让工具说话:我在 Inspira Board 里用 AI 把“能用、好用、可复用”落成了日常
前端
weixin_456904274 小时前
C# 中的回调函数
java·前端·c#
kura_tsuki5 小时前
[Web网页] LAMP 架构与环境搭建
前端·架构
yinuo5 小时前
UniApp+Vue3多分包引入同一 npm 库被重复打包至 vendor 的问题分析与解决
前端
码界奇点5 小时前
Spring Web MVC构建现代Java Web应用的基石
java·前端·spring·设计规范
苏打水com5 小时前
JavaScript 入门学习指南:从零基础到能写交互效果
javascript
yinuo5 小时前
UniApp + Vue3 使用 marked 报错:SyntaxError /[\p{L}\p{N}]/u 问题分析与解决
前端