Watch源码深度解读

37 阅读4分钟
  • watch的核心思想: 创建一个响应式副作用,当依赖的响应式数据变化时执行回调或重新运行副作用

deepseek_mermaid_20260122_0e201c.png

源码

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  // 恢复
      }
    }
  }
}
  • 两种模式

    1. watch(getter, cb):getter 返回被监听的值
    2. 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 抽象
  • 类型安全的渐进增强
  • 资源生命周期管理