- 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
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.`,
)
}
const reactiveGetter = (source: object) => {
// traverse will happen in wrapped getter below
if (deep) return source
// for `deep: false | 0` or shallow reactive, only traverse root-level properties
if (isShallow(source) || deep === false || deep === 0)
return traverse(source, 1)
// for `deep: undefined` on a reactive object, deeply traverse all properties
return traverse(source)
}
let effect: ReactiveEffect
let getter: () => any
let cleanup: (() => void) | undefined
let boundCleanup: typeof onWatcherCleanup
let forceTrigger = false
let isMultiSource = false
if (isRef(source)) {
getter = () => source.value
forceTrigger = isShallow(source)
} else if (isReactive(source)) {
getter = () => reactiveGetter(source)
forceTrigger = true
} else if (isArray(source)) {
isMultiSource = true
forceTrigger = source.some(s => isReactive(s) || isShallow(s))
getter = () =>
source.map(s => {
if (isRef(s)) {
return s.value
} else if (isReactive(s)) {
return reactiveGetter(s)
} else if (isFunction(s)) {
return call ? call(s, WatchErrorCodes.WATCH_GETTER) : s()
} else {
__DEV__ && warnInvalidSource(s)
}
})
} else if (isFunction(source)) {
if (cb) {
// getter with cb
getter = call
? () => call(source, WatchErrorCodes.WATCH_GETTER)
: (source as () => any)
} else {
// no cb -> simple effect
getter = () => {
if (cleanup) {
pauseTracking()
try {
cleanup()
} finally {
resetTracking()
}
}
const currentEffect = activeWatcher
activeWatcher = effect
try {
return call
? call(source, WatchErrorCodes.WATCH_CALLBACK, [boundCleanup])
: source(boundCleanup)
} finally {
activeWatcher = currentEffect
}
}
}
} else {
getter = NOOP
__DEV__ && warnInvalidSource(source)
}
if (cb && deep) {
const baseGetter = getter
const depth = deep === true ? Infinity : deep
getter = () => traverse(baseGetter(), depth)
}
const scope = getCurrentScope()
const watchHandle: WatchHandle = () => {
effect.stop()
if (scope && scope.active) {
remove(scope.effects, effect)
}
}
if (once && cb) {
const _cb = cb
cb = (...args) => {
_cb(...args)
watchHandle()
}
}
let oldValue: any = isMultiSource
? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
: INITIAL_WATCHER_VALUE
const job = (immediateFirstRun?: boolean) => {
if (
!(effect.flags & EffectFlags.ACTIVE) ||
(!effect.dirty && !immediateFirstRun)
) {
return
}
if (cb) {
// watch(source, cb)
const newValue = effect.run()
if (
deep ||
forceTrigger ||
(isMultiSource
? (newValue as any[]).some((v, i) => hasChanged(v, oldValue[i]))
: hasChanged(newValue, oldValue))
) {
// cleanup before running cb again
if (cleanup) {
cleanup()
}
const currentWatcher = activeWatcher
activeWatcher = effect
try {
const args = [
newValue,
// pass undefined as the old value when it's changed for the first time
oldValue === INITIAL_WATCHER_VALUE
? undefined
: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
? []
: oldValue,
boundCleanup,
]
oldValue = newValue
call
? call(cb!, WatchErrorCodes.WATCH_CALLBACK, args)
: // @ts-expect-error
cb!(...args)
} finally {
activeWatcher = currentWatcher
}
}
} else {
// watchEffect
effect.run()
}
}
if (augmentJob) {
augmentJob(job)
}
effect = new ReactiveEffect(getter)
effect.scheduler = scheduler
? () => scheduler(job, false)
: (job as EffectScheduler)
boundCleanup = fn => onWatcherCleanup(fn, false, effect)
cleanup = effect.onStop = () => {
const cleanups = cleanupMap.get(effect)
if (cleanups) {
if (call) {
call(cleanups, WatchErrorCodes.WATCH_CLEANUP)
} else {
for (const cleanup of cleanups) cleanup()
}
cleanupMap.delete(effect)
}
}
if (__DEV__) {
effect.onTrack = options.onTrack
effect.onTrigger = options.onTrigger
}
// initial run
if (cb) {
if (immediate) {
job(true)
} else {
oldValue = effect.run()
}
} else if (scheduler) {
scheduler(job.bind(null, true), true)
} else {
effect.run()
}
watchHandle.pause = effect.pause.bind(effect)
watchHandle.resume = effect.resume.bind(effect)
watchHandle.stop = watchHandle
return watchHandle
}
逐段分析
1. 参数解析与配置提取
typescript
const { immediate, deep, once, scheduler, augmentJob, call } = options
-immediate:是否立即执行回调
- deep:深度监听,可以为数字,表示最多监听到多少层
- once:只执行一次回调
- scheduler:自定义调度器(控制回调执行实际)
- augmentJob:增强任务(高级定制)
- call:SSR安全调用(错误处理)
2. 源数据(source)类型判断与 getter 创建
Case 1: Ref 类型
typescript
if (isRef(source)) {
getter = () => source.value
forceTrigger = isShallow(source)
}
- 优化:直接访问
.value,无需代理 forceTrigger:浅层 ref 需要强制触发(值变化但引用未变)
Case 2: Reactive 对象
typescript
else if (isReactive(source)) {
getter = () => reactiveGetter(source)
forceTrigger = true
}
-
关键:
forceTrigger = true- reactive 对象任何属性变化都应触发 -
reactiveGetter函数:typescript
const reactiveGetter = (source: object) => { if (deep) return source // deep: true 深度遍历 if (isShallow(source) || deep === false || deep === 0) return traverse(source, 1) // 只遍历第一层 return traverse(source) // 默认深度遍历 }Case 3: 数组类型(多源监听)
typescript
else if (isArray(source)) {
isMultiSource = true
forceTrigger = source.some(s => isReactive(s) || isShallow(s))
getter = () =>
source.map(s => {
if (isRef(s)) return s.value
else if (isReactive(s)) return reactiveGetter(s)
else if (isFunction(s)) return call ? call(s) : s()
else __DEV__ && warnInvalidSource(s)
})
}
- 多源监听:同时监听多个数据源
- 智能 forceTrigger:数组中只要有一个 reactive 或 shallow 对象就强制触发
- 统一返回值:数组形式,保持源顺序
Case 4: 函数类型
typescript
else if (isFunction(source)) {
if (cb) {
// watch(source, cb) 模式
getter = call ? () => call(source) : source
} else {
// watchEffect 模式(无 cb)
getter = () => {
if (cleanup) { /* 执行清理函数 */ }
const currentEffect = activeWatcher
activeWatcher = effect // 设置当前活动的 watcher
try {
return call ? call(source, [boundCleanup]) : source(boundCleanup)
} finally {
activeWatcher = currentEffect // 恢复
}
}
}
}
-
两种模式:
watch(getter, cb):getter 返回被监听的值watchEffect(effect):effect 函数本身就是副作用
Case 5: 无效源
typescript
else {
getter = NOOP // 空函数
__DEV__ && warnInvalidSource(source)
}
3. 深度监听(deep)的增强处理
typescript
if (cb && deep) {
const baseGetter = getter
const depth = deep === true ? Infinity : deep // 支持自定义深度
getter = () => traverse(baseGetter(), depth)
}
traverse函数:递归遍历对象所有属性,建立依赖- 灵活深度控制:
deep: 2只递归两层
4. ReactiveEffect 创建与调度
typescript
effect = new ReactiveEffect(getter)
effect.scheduler = scheduler
? () => scheduler(job, false)
: (job as EffectScheduler)
- 核心:创建响应式副作用
- 调度器:支持自定义调度(如微任务、动画帧等)
5. 回调任务(job)的智能执行
typescript
const job = (immediateFirstRun?: boolean) => {
// 1. 检查是否应该执行
if (!(effect.flags & EffectFlags.ACTIVE) || (!effect.dirty && !immediateFirstRun)) {
return
}
if (cb) {
// watch 模式
const newValue = effect.run()
if (deep || forceTrigger || hasChanged(newValue, oldValue)) {
// 执行清理 → 更新 oldValue → 执行回调
}
} else {
// watchEffect 模式:直接运行
effect.run()
}
}
变化检测的优化策略:
typescript
// 多源:逐个比较
isMultiSource
? (newValue as any[]).some((v, i) => hasChanged(v, oldValue[i]))
: hasChanged(newValue, oldValue)
// 强制触发条件:
// 1. deep: true(深度监听)
// 2. forceTrigger: true(reactive/shallow 对象)
// 3. 值确实发生了变化
6. 清理(cleanup)机制
typescript
boundCleanup = fn => onWatcherCleanup(fn, false, effect)
cleanup = effect.onStop = () => {
const cleanups = cleanupMap.get(effect)
if (cleanups) {
if (call) {
call(cleanups, WatchErrorCodes.WATCH_CLEANUP)
} else {
for (const cleanup of cleanups) cleanup()
}
cleanupMap.delete(effect)
}
}
- 异步清理:支持在回调中注册清理函数
- 自动清理:watcher 停止时自动执行所有清理函数
7. 初始运行策略
typescript
// 有回调的情况
if (cb) {
if (immediate) {
job(true) // 立即执行
} else {
oldValue = effect.run() // 只收集初始值
}
}
// watchEffect 模式
else if (scheduler) {
scheduler(job.bind(null, true), true) // 调度执行
} else {
effect.run() // 立即执行
}
8. 返回的 WatchHandle 对象
typescript
const watchHandle: WatchHandle = () => {
effect.stop()
if (scope && scope.active) {
remove(scope.effects, effect)
}
}
watchHandle.pause = effect.pause.bind(effect)
watchHandle.resume = effect.resume.bind(effect)
watchHandle.stop = watchHandle
return watchHandle
- 完整控制:停止、暂停、恢复
- 作用域集成:自动从作用域中清理
总结(设计亮点)
- 统一的 getter 抽象
- 类型安全的渐进增强
- 资源生命周期管理