Skip to content

第3.5节:watch 与 watchEffect:侦听器的实现原理与差异对比

侦听器(Watcher)是Vue.js 3响应式系统中的重要组成部分,它允许开发者在响应式数据变化时执行特定的副作用。Vue 3提供了两种主要的侦听器API:watchwatchEffect,它们在使用方式和内部实现上有着显著的差异。本节将深入分析这两种侦听器的实现原理,探讨它们的设计思路和应用场景。

3.5.1 侦听器系统的整体架构

核心组件关系

Vue 3的侦听器系统建立在响应式系统之上,主要包含以下核心组件:

typescript
// 侦听器的核心类型定义
export type WatchSource<T = any> = Ref<T, any> | ComputedRef<T> | (() => T)
export type WatchEffect = (onCleanup: OnCleanup) => void
export type WatchCallback<V = any, OV = any> = (
  value: V,
  oldValue: OV,
  onCleanup: OnCleanup,
) => any

// 侦听器选项
export interface WatchOptions<Immediate = boolean> extends DebuggerOptions {
  immediate?: Immediate
  deep?: boolean | number
  once?: boolean
  scheduler?: WatchScheduler
  flush?: 'pre' | 'post' | 'sync'
}

// 侦听器句柄
export interface WatchHandle extends WatchStopHandle {
  pause: () => void
  resume: () => void
  stop: () => void
}

双层架构设计

Vue 3的侦听器采用了双层架构设计:

  1. 底层实现@vue/reactivity包中的watch函数(baseWatch)
  2. 上层封装@vue/runtime-core包中的watchwatchEffect等API

这种设计使得响应式系统可以独立使用,同时为运行时提供了更丰富的功能。

3.5.2 watch:精确的数据侦听

基本用法与特点

watch函数用于侦听特定的响应式数据源,当数据发生变化时执行回调函数:

typescript
// 侦听单个数据源
const count = ref(0)
watch(count, (newValue, oldValue) => {
  console.log(`count changed: ${oldValue} -> ${newValue}`)
})

// 侦听多个数据源
const name = ref('Alice')
const age = ref(25)
watch([name, age], ([newName, newAge], [oldName, oldAge]) => {
  console.log(`User changed: ${oldName}(${oldAge}) -> ${newName}(${newAge})`)
})

// 侦听响应式对象
const state = reactive({ count: 0, name: 'Vue' })
watch(
  () => state.count,
  (newCount) => {
    console.log(`State count: ${newCount}`)
  }
)

watch的核心实现

1. 多重函数重载

watch函数通过TypeScript的函数重载支持多种使用方式:

typescript
// 单个数据源
export function watch<T, Immediate extends Readonly<boolean> = false>(
  source: WatchSource<T>,
  cb: WatchCallback<T, MaybeUndefined<T, Immediate>>,
  options?: WatchOptions<Immediate>,
): WatchHandle

// 多个数据源(只读数组)
export function watch<
  T extends Readonly<MultiWatchSources>,
  Immediate extends Readonly<boolean> = false,
>(
  sources: readonly [...T] | T,
  cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
  options?: WatchOptions<Immediate>,
): WatchHandle

// 响应式对象
export function watch<
  T extends object,
  Immediate extends Readonly<boolean> = false,
>(
  source: T,
  cb: WatchCallback<T, MaybeUndefined<T, Immediate>>,
  options?: WatchOptions<Immediate>,
): WatchHandle

2. doWatch:统一的实现入口

所有的watch重载最终都会调用doWatch函数:

typescript
function doWatch(
  source: WatchSource | WatchSource[] | WatchEffect | object,
  cb: WatchCallback | null,
  options: WatchOptions = EMPTY_OBJ,
): WatchHandle {
  const { immediate, deep, flush, once } = options

  // 参数验证
  if (__DEV__ && !cb) {
    if (immediate !== undefined) {
      warn('watch() "immediate" option is only respected when using the watch(source, callback, options?) signature.')
    }
    // ... 其他警告
  }

  // 创建基础选项
  const baseWatchOptions: BaseWatchOptions = extend({}, options)
  if (__DEV__) baseWatchOptions.onWarn = warn

  // 调度器配置
  const instance = currentInstance
  baseWatchOptions.call = (fn, type, args) =>
    callWithAsyncErrorHandling(fn, instance, type, args)

  // 设置调度策略
  let isPre = false
  if (flush === 'post') {
    baseWatchOptions.scheduler = job => {
      queuePostRenderEffect(job, instance && instance.suspense)
    }
  } else if (flush !== 'sync') {
    // default: 'pre'
    isPre = true
    baseWatchOptions.scheduler = (job, isFirstRun) => {
      if (isFirstRun) {
        job()
      } else {
        queueJob(job)
      }
    }
  }

  // 调用底层watch函数
  const watchHandle = baseWatch(source, cb, baseWatchOptions)
  return watchHandle
}

3. 底层watch函数的实现

底层watch函数是侦听器的核心实现:

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

  // 数据源处理
  let effect: ReactiveEffect
  let getter: () => any
  let cleanup: (() => void) | undefined
  let forceTrigger = false
  let isMultiSource = false

  // 根据数据源类型创建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
    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 = call
        ? () => call(source, WatchErrorCodes.WATCH_GETTER)
        : (source as () => any)
    } else {
      // watchEffect模式
      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
        }
      }
    }
  }

  // 深度侦听处理
  if (cb && deep) {
    const baseGetter = getter
    const depth = deep === true ? Infinity : deep
    getter = () => traverse(baseGetter(), depth)
  }

  // 创建ReactiveEffect
  effect = new ReactiveEffect(getter)
  effect.scheduler = scheduler
    ? () => scheduler(job, false)
    : (job as EffectScheduler)

  // 初始执行
  if (cb) {
    if (immediate) {
      job(true)
    } else {
      oldValue = effect.run()
    }
  } else {
    effect.run()
  }

  return watchHandle
}

3.5.3 watchEffect:自动依赖收集的副作用

基本用法与特点

watchEffect会立即执行传入的函数,并自动收集函数内部使用的响应式依赖:

typescript
// 基本用法
const count = ref(0)
const name = ref('Vue')

watchEffect(() => {
  console.log(`${name.value}: ${count.value}`)
})

// 清理副作用
watchEffect((onCleanup) => {
  const timer = setInterval(() => {
    console.log('Timer tick')
  }, 1000)
  
  onCleanup(() => {
    clearInterval(timer)
  })
})

watchEffect的实现

watchEffect实际上是watch函数的一个特殊用法:

typescript
export function watchEffect(
  effect: WatchEffect,
  options?: WatchEffectOptions,
): WatchHandle {
  return doWatch(effect, null, options)
}

export function watchPostEffect(
  effect: WatchEffect,
  options?: DebuggerOptions,
): WatchHandle {
  return doWatch(
    effect,
    null,
    __DEV__ ? extend({}, options as any, { flush: 'post' }) : { flush: 'post' },
  )
}

export function watchSyncEffect(
  effect: WatchEffect,
  options?: DebuggerOptions,
): WatchHandle {
  return doWatch(
    effect,
    null,
    __DEV__ ? extend({}, options as any, { flush: 'sync' }) : { flush: 'sync' },
  )
}

关键特点:

  1. 无回调函数cb参数为null
  2. 自动依赖收集:在函数执行过程中自动收集依赖
  3. 立即执行:创建后立即执行一次

3.5.4 深度侦听:deep选项的实现

traverse函数:深度遍历的核心

深度侦听通过traverse函数实现,它会递归遍历对象的所有属性:

typescript
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: any) => {
      traverse(v, depth, seen)
    })
  } else if (isPlainObject(value)) {
    // 遍历对象属性
    for (const key in value) {
      traverse(value[key], depth, seen)
    }
    // 遍历Symbol属性
    for (const key of Object.getOwnPropertySymbols(value)) {
      if (Object.prototype.propertyIsEnumerable.call(value, key)) {
        traverse(value[key as any], depth, seen)
      }
    }
  }
  return value
}

深度控制策略

typescript
const reactiveGetter = (source: object) => {
  // 深度侦听
  if (deep) return source
  
  // 浅层响应式或明确指定浅层
  if (isShallow(source) || deep === false || deep === 0)
    return traverse(source, 1)
  
  // 默认深度遍历
  return traverse(source)
}

// 在watch中应用深度侦听
if (cb && deep) {
  const baseGetter = getter
  const depth = deep === true ? Infinity : deep
  getter = () => traverse(baseGetter(), depth)
}

深度侦听的性能考虑

  1. 循环引用检测:使用Map记录已访问的对象,避免无限递归
  2. 深度限制:支持数字类型的deep选项,限制遍历深度
  3. 类型优化:针对不同数据类型采用最优的遍历策略

3.5.5 立即执行:immediate选项

immediate的实现机制

typescript
// 在watch函数中的处理
const job = (immediateFirstRun?: boolean) => {
  if (
    !(effect.flags & EffectFlags.ACTIVE) ||
    (!effect.dirty && !immediateFirstRun)
  ) {
    return
  }
  
  if (cb) {
    const newValue = effect.run()
    if (
      deep ||
      forceTrigger ||
      (isMultiSource
        ? (newValue as any[]).some((v, i) => hasChanged(v, oldValue[i]))
        : hasChanged(newValue, oldValue))
    ) {
      // 执行回调
      const args = [
        newValue,
        oldValue === INITIAL_WATCHER_VALUE
          ? undefined
          : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
            ? []
            : oldValue,
        boundCleanup,
      ]
      oldValue = newValue
      call
        ? call(cb!, WatchErrorCodes.WATCH_CALLBACK, args)
        : cb!(...args)
    }
  }
}

// 初始执行逻辑
if (cb) {
  if (immediate) {
    job(true) // 立即执行
  } else {
    oldValue = effect.run() // 只收集依赖,不执行回调
  }
} else {
  effect.run() // watchEffect总是立即执行
}

immediate的特殊处理

  1. 初始值处理:第一次执行时,oldValueundefined
  2. 多数据源:数组形式的数据源,初始值为空数组
  3. 强制触发:使用immediateFirstRun标志强制执行回调

3.5.6 清理机制:onCleanup的作用

清理函数的设计

typescript
// 清理函数映射表
const cleanupMap: WeakMap<ReactiveEffect, (() => void)[]> = new WeakMap()
let activeWatcher: ReactiveEffect | undefined = undefined

// 注册清理函数
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` +
        ` to associate with.`,
    )
  }
}

清理时机

typescript
// 在副作用重新执行前清理
const job = (immediateFirstRun?: boolean) => {
  if (cb) {
    const newValue = effect.run()
    if (/* 需要触发回调 */) {
      // 清理上一次的副作用
      if (cleanup) {
        cleanup()
      }
      
      // 执行新的回调
      const currentWatcher = activeWatcher
      activeWatcher = effect
      try {
        cb!(newValue, oldValue, boundCleanup)
      } finally {
        activeWatcher = currentWatcher
      }
    }
  }
}

// 停止侦听器时清理
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)
  }
}

清理机制的应用场景

typescript
// 1. 清理定时器
watchEffect((onCleanup) => {
  const timer = setInterval(() => {
    console.log('tick')
  }, 1000)
  
  onCleanup(() => clearInterval(timer))
})

// 2. 清理事件监听器
watchEffect((onCleanup) => {
  const handler = () => console.log('click')
  document.addEventListener('click', handler)
  
  onCleanup(() => {
    document.removeEventListener('click', handler)
  })
})

// 3. 取消网络请求
watchEffect((onCleanup) => {
  const controller = new AbortController()
  
  fetch('/api/data', { signal: controller.signal })
    .then(response => response.json())
    .then(data => console.log(data))
  
  onCleanup(() => controller.abort())
})

3.5.7 调度系统:flush选项的实现

三种调度策略

Vue 3提供了三种不同的调度策略:

typescript
// 1. 'pre':组件更新前执行(默认)
if (flush !== 'sync') {
  isPre = true
  baseWatchOptions.scheduler = (job, isFirstRun) => {
    if (isFirstRun) {
      job() // 首次立即执行
    } else {
      queueJob(job) // 加入pre队列
    }
  }
}

// 2. 'post':组件更新后执行
if (flush === 'post') {
  baseWatchOptions.scheduler = job => {
    queuePostRenderEffect(job, instance && instance.suspense)
  }
}

// 3. 'sync':同步执行
// 不设置scheduler,直接同步执行

调度队列的管理

typescript
// 标记任务类型
baseWatchOptions.augmentJob = (job: SchedulerJob) => {
  if (cb) {
    job.flags! |= SchedulerJobFlags.ALLOW_RECURSE
  }
  if (isPre) {
    job.flags! |= SchedulerJobFlags.PRE
    if (instance) {
      job.id = instance.uid
      ;(job as SchedulerJob).i = instance
    }
  }
}

3.5.8 高级特性:侦听多个源

数组形式的watch

typescript
// 多数据源的类型定义
export type MultiWatchSources = (WatchSource<unknown> | object)[]

type MapSources<T, Immediate> = {
  [K in keyof T]: T[K] extends WatchSource<infer V>
    ? MaybeUndefined<V, Immediate>
    : T[K] extends object
      ? MaybeUndefined<T[K], Immediate>
      : never
}

// 多数据源的处理逻辑
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)
    }
  })
}

多数据源的变化检测

typescript
// 初始值处理
let oldValue: any = isMultiSource
  ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
  : INITIAL_WATCHER_VALUE

// 变化检测
if (
  deep ||
  forceTrigger ||
  (isMultiSource
    ? (newValue as any[]).some((v, i) => hasChanged(v, oldValue[i]))
    : hasChanged(newValue, oldValue))
) {
  // 触发回调
}

3.5.9 异步侦听器:Promise和async/await

异步回调的处理

typescript
// 异步watch回调
watch(source, async (newValue, oldValue) => {
  try {
    const result = await fetchData(newValue)
    console.log('Data fetched:', result)
  } catch (error) {
    console.error('Fetch failed:', error)
  }
})

// 带清理的异步watchEffect
watchEffect(async (onCleanup) => {
  let cancelled = false
  
  onCleanup(() => {
    cancelled = true
  })
  
  try {
    const data = await fetchData()
    if (!cancelled) {
      console.log('Data:', data)
    }
  } catch (error) {
    if (!cancelled) {
      console.error('Error:', error)
    }
  }
})

错误处理机制

typescript
// 错误处理的实现
baseWatchOptions.call = (fn, type, args) =>
  callWithAsyncErrorHandling(fn, instance, type, args)

// 在callWithAsyncErrorHandling中
export function callWithAsyncErrorHandling(
  fn: Function | Function[],
  instance: ComponentInternalInstance | null,
  type: ErrorTypes,
  args?: unknown[],
): any[] {
  if (isFunction(fn)) {
    const res = callWithErrorHandling(fn, instance, type, args)
    if (res && isPromise(res)) {
      res.catch(err => {
        handleError(err, instance, type)
      })
    }
    return res
  }
  // 处理函数数组...
}

3.5.10 停止侦听:WatchHandle的实现

WatchHandle接口

typescript
export interface WatchHandle extends WatchStopHandle {
  pause: () => void   // 暂停侦听
  resume: () => void  // 恢复侦听
  stop: () => void    // 停止侦听
}

// 创建WatchHandle
const scope = getCurrentScope()
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

生命周期管理

typescript
// 自动清理机制
if (instance) {
  // 组件卸载时自动停止侦听器
  onBeforeUnmount(() => {
    watchHandle.stop()
  }, instance)
}

// 作用域管理
if (scope) {
  scope.effects.push(effect)
}

3.5.11 watch vs watchEffect:核心差异对比

功能对比表

特性watchwatchEffect
数据源明确指定自动收集
回调函数必需不需要
旧值访问支持不支持
立即执行可选(immediate)总是立即执行
惰性执行默认惰性不支持
深度侦听支持deep选项自动深度
清理函数onCleanup参数onCleanup参数
类型推导精确的类型较弱的类型

使用场景对比

watch适用场景

typescript
// 1. 需要访问旧值
watch(count, (newVal, oldVal) => {
  console.log(`Count changed from ${oldVal} to ${newVal}`)
})

// 2. 惰性执行
watch(expensiveData, (newData) => {
  // 只在数据真正变化时执行昂贵操作
  processExpensiveData(newData)
})

// 3. 精确控制侦听源
watch(
  () => state.user.profile.name,
  (newName) => {
    updateUserName(newName)
  }
)

// 4. 条件侦听
watch(
  source,
  (newVal) => {
    if (condition) {
      doSomething(newVal)
    }
  },
  { immediate: false }
)

watchEffect适用场景

typescript
// 1. 自动依赖收集
watchEffect(() => {
  // 自动侦听所有使用的响应式数据
  document.title = `${user.name} - ${page.title}`
})

// 2. 副作用同步
watchEffect(() => {
  if (isLoggedIn.value) {
    startHeartbeat()
  } else {
    stopHeartbeat()
  }
})

// 3. 简单的响应式计算
watchEffect(() => {
  localStorage.setItem('settings', JSON.stringify(settings))
})

// 4. DOM操作
watchEffect(() => {
  if (showModal.value) {
    document.body.style.overflow = 'hidden'
  } else {
    document.body.style.overflow = ''
  }
})

性能对比

watch的性能特点

  1. 精确依赖:只侦听明确指定的数据源
  2. 惰性执行:可以避免不必要的初始执行
  3. 浅层侦听:默认只侦听引用变化,性能更好
  4. 条件执行:可以在回调中添加条件判断

watchEffect的性能特点

  1. 自动依赖:可能收集到不必要的依赖
  2. 立即执行:总是会执行一次,可能造成浪费
  3. 深度侦听:自动深度侦听所有访问的属性
  4. 简洁代码:减少样板代码,提高开发效率

3.5.12 once选项:一次性侦听器

once的实现机制

typescript
// once选项的处理
if (once && cb) {
  const _cb = cb
  cb = (...args) => {
    _cb(...args)
    watchHandle() // 执行一次后自动停止
  }
}

使用场景

typescript
// 等待某个条件满足
watch(
  () => user.isLoaded,
  (isLoaded) => {
    if (isLoaded) {
      initializeApp()
    }
  },
  { once: true }
)

// 监听首次数据变化
watch(
  data,
  (newData) => {
    trackFirstDataChange(newData)
  },
  { once: true }
)

3.5.13 错误处理与调试

错误类型定义

typescript
export enum WatchErrorCodes {
  WATCH_GETTER = 2,
  WATCH_CALLBACK,
  WATCH_CLEANUP,
}

调试支持

typescript
// 开发模式下的调试选项
if (__DEV__) {
  effect.onTrack = options.onTrack
  effect.onTrigger = options.onTrigger
}

// 使用调试选项
watch(
  source,
  callback,
  {
    onTrack(e) {
      console.log('依赖收集:', e)
    },
    onTrigger(e) {
      console.log('触发更新:', e)
    }
  }
)

常见错误处理

typescript
// 无效数据源警告
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.`,
  )
}

// 选项验证
if (__DEV__ && !cb) {
  if (immediate !== undefined) {
    warn(
      `watch() "immediate" option is only respected when using the ` +
        `watch(source, callback, options?) signature.`,
    )
  }
}

3.5.14 SSR特殊处理

SSR环境下的侦听器

typescript
// SSR环境检测
let ssrCleanup: (() => void)[] | undefined
if (__SSR__ && isInSSRComponentSetup) {
  if (flush === 'sync') {
    const ctx = useSSRContext()!
    ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = [])
  } else if (!runsImmediately) {
    // 在SSR中,非立即执行的侦听器返回空句柄
    const watchStopHandle = () => {}
    watchStopHandle.stop = NOOP
    watchStopHandle.resume = NOOP
    watchStopHandle.pause = NOOP
    return watchStopHandle
  }
}

// SSR清理处理
if (__SSR__ && isInSSRComponentSetup) {
  if (ssrCleanup) {
    ssrCleanup.push(watchHandle)
  } else if (runsImmediately) {
    watchHandle()
  }
}

3.5.15 实际应用场景与最佳实践

1. 数据同步

typescript
// 同步本地存储
watch(
  () => userSettings,
  (settings) => {
    localStorage.setItem('userSettings', JSON.stringify(settings))
  },
  { deep: true }
)

// 同步URL参数
watch(
  () => route.query,
  (query) => {
    updateFilters(query)
  },
  { immediate: true }
)

2. 副作用管理

typescript
// 管理WebSocket连接
watchEffect((onCleanup) => {
  if (isConnected.value) {
    const ws = new WebSocket(wsUrl.value)
    
    ws.onmessage = (event) => {
      handleMessage(event.data)
    }
    
    onCleanup(() => {
      ws.close()
    })
  }
})

// 管理定时任务
watchEffect((onCleanup) => {
  if (autoRefresh.value) {
    const timer = setInterval(() => {
      refreshData()
    }, refreshInterval.value)
    
    onCleanup(() => {
      clearInterval(timer)
    })
  }
})

3. 性能优化

typescript
// 防抖处理
const debouncedWatch = (source, callback, delay = 300) => {
  let timer: number
  
  return watch(source, (...args) => {
    clearTimeout(timer)
    timer = setTimeout(() => {
      callback(...args)
    }, delay)
  })
}

// 节流处理
const throttledWatch = (source, callback, delay = 300) => {
  let lastCall = 0
  
  return watch(source, (...args) => {
    const now = Date.now()
    if (now - lastCall >= delay) {
      lastCall = now
      callback(...args)
    }
  })
}

4. 条件侦听

typescript
// 条件激活的侦听器
const conditionalWatch = (condition, source, callback) => {
  let stopWatcher: (() => void) | null = null
  
  watchEffect(() => {
    if (condition.value) {
      if (!stopWatcher) {
        stopWatcher = watch(source, callback)
      }
    } else {
      if (stopWatcher) {
        stopWatcher()
        stopWatcher = null
      }
    }
  })
}

3.5.16 最佳实践建议

1. 选择合适的侦听器类型

typescript
// ✅ 使用watch当需要旧值或惰性执行
watch(count, (newVal, oldVal) => {
  console.log(`Changed from ${oldVal} to ${newVal}`)
})

// ✅ 使用watchEffect当需要自动依赖收集
watchEffect(() => {
  document.title = `${user.name} - ${page.title}`
})

// ❌ 避免在watchEffect中使用不相关的响应式数据
watchEffect(() => {
  if (someCondition.value) {
    // 这会导致不必要的依赖收集
    console.log(unrelatedData.value)
  }
})

2. 合理使用深度侦听

typescript
// ✅ 只在必要时使用深度侦听
watch(
  () => user.profile,
  (profile) => {
    updateProfile(profile)
  },
  { deep: true }
)

// ✅ 使用具体路径避免深度侦听
watch(
  () => user.profile.name,
  (name) => {
    updateUserName(name)
  }
)

// ❌ 避免对大对象进行深度侦听
watch(
  largeDataSet,
  () => {
    // 这可能导致性能问题
  },
  { deep: true }
)

3. 正确处理清理逻辑

typescript
// ✅ 总是清理副作用
watchEffect((onCleanup) => {
  const subscription = subscribe()
  onCleanup(() => subscription.unsubscribe())
})

// ✅ 在组件卸载时停止侦听器
const stopWatcher = watch(source, callback)
onBeforeUnmount(() => {
  stopWatcher()
})

// ❌ 忘记清理可能导致内存泄漏
watchEffect(() => {
  setInterval(() => {
    // 没有清理定时器
  }, 1000)
})

4. 避免常见陷阱

typescript
// ❌ 在侦听器中修改被侦听的数据(可能导致无限循环)
watch(count, () => {
  count.value++ // 危险!
})

// ✅ 使用条件判断避免无限循环
watch(count, (newVal) => {
  if (newVal < 10) {
    count.value++
  }
})

// ❌ 侦听非响应式数据
let plainValue = 0
watch(
  () => plainValue, // 不会触发
  (val) => console.log(val)
)

// ✅ 确保侦听响应式数据
const reactiveValue = ref(0)
watch(reactiveValue, (val) => console.log(val))

总结

Vue.js 3的侦听器系统通过精心设计的架构提供了强大而灵活的响应式副作用管理能力:

核心设计原则

  1. 双层架构:底层响应式包提供核心功能,上层运行时包提供丰富的API
  2. 类型安全:通过TypeScript重载提供精确的类型推导
  3. 性能优化:支持多种调度策略和深度控制选项
  4. 清理机制:完善的副作用清理和内存管理
  5. 错误处理:统一的错误处理和调试支持

技术亮点

  1. 智能依赖收集watchEffect的自动依赖收集机制
  2. 精确变化检测watch的精确数据源侦听
  3. 深度遍历优化traverse函数的循环引用检测和深度控制
  4. 调度系统集成:与Vue的调度系统无缝集成
  5. SSR兼容性:完善的服务端渲染支持

实践价值

  1. 开发效率:简洁的API设计提高开发效率
  2. 性能保障:多种优化策略确保应用性能
  3. 类型安全:TypeScript支持减少运行时错误
  4. 调试友好:丰富的调试选项和错误信息
  5. 生态兼容:与Vue生态系统完美集成

理解侦听器的实现原理不仅有助于更好地使用这些API,还能帮助开发者在构建复杂应用时做出更明智的架构决策。


微信公众号二维码