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