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) - 工具函数(
hasChanged、remove) - 生命周期追踪与暂停追踪(
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 策略(例如
post、sync)。
初始化:
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
}
作用:递归访问对象的所有属性,从而触发依赖收集(用于
deepwatch)。
八、总结
✨ watch 的核心思想:
- 根据
source类型创建 getter。 - 使用
ReactiveEffect追踪依赖。 - 当依赖变化时执行
job,触发回调。 - 支持
immediate、deep、once等灵活配置。 - 通过
traverse实现深层依赖收集。 - 提供
onWatcherCleanup实现副作用清理机制。
⚙️ watch 与 watchEffect 的区别:
| 特性 | watch | watchEffect |
|---|---|---|
| 参数 | 源 + 回调 | 仅副作用函数 |
| 取值方式 | 比较新旧值 | 自动执行函数内部依赖 |
| 是否传参 | 需要指定 source | 自动收集依赖 |
| 使用场景 | 精确侦听某个数据变化 | 响应式副作用逻辑 |
📘 阅读建议
若要深入理解 watch:
- 先阅读
ReactiveEffect的实现; - 再研究
track/trigger; - 理解
traverse如何触发深度依赖; - 最后再看
scheduler如何与flush配合。
本文内容由人工智能生成,仅供学习与参考使用,请在实际应用中结合自身情况进行判断。