【Vue3 源码解析】watch 侦听器

export function watch<T = any, Immediate extends Readonly<boolean> = false>(
  source: T | WatchSource<T>,
  cb: any,
  options?: WatchOptions<Immediate>
): WatchStopHandle {
  // 在开发环境下,如果回调函数不是函数,则发出警告
  if (__DEV__ && !isFunction(cb)) {
    warn(
      `\`watch(fn, options?)\` signature has been moved to a separate API. ` +
        `Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +
        `supports \`watch(source, cb, options?) signature.`
    )
  }
  // 调用 doWatch 函数来执行监视操作
  return doWatch(source as any, cb, options)
}

这段代码是 watch 函数的一个重载形式,用于在开发环境下检查回调函数的类型,并调用 doWatch 函数来实际执行监视操作。

解释:

  • 这段代码导出了一个名为 watch 的函数,它接受三个参数:sourcecboptionssource 表示要监视的数据源,cb 表示回调函数,options 表示监视的配置选项。

  • 在开发环境下,通过 __DEV__ 条件判断检查回调函数 cb 是否是一个函数。如果不是函数,则发出警告,提示用户 watch(fn, options?) 的签名已经被移动到单独的 API,建议使用 watchEffect(fn, options?) 代替。

  • 最后,调用 doWatch 函数来执行监视操作,并返回一个 WatchStopHandle 函数,用于停止监视。

function doWatch(
  source: WatchSource | WatchSource[] | WatchEffect | object,
  cb: WatchCallback | null,
  { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
): WatchStopHandle {
  // 在开发环境下,如果没有提供回调函数 cb,但提供了 immediate 或 deep 选项,
  // 则发出警告,因为这些选项只对特定的签名有效。
  if (__DEV__ && !cb) {
    if (immediate !== undefined) {
      warn(
        `watch() "immediate" option is only respected when using the ` +
          `watch(source, callback, options?) signature.`
      )
    }
    if (deep !== undefined) {
      warn(
        `watch() "deep" option is only respected when using the ` +
          `watch(source, callback, options?) signature.`
      )
    }
  }

  // 定义用于警告无效监视源的函数
  const warnInvalidSource = (s: unknown) => {
    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 instance =
    getCurrentScope() === currentInstance?.scope ? currentInstance : null

  let getter: () => any
  let forceTrigger = false
  let isMultiSource = false

  // 根据不同类型的数据源,设置相应的 getter 和 forceTrigger 值
  // 如果是 ref 对象
  if (isRef(source)) {
  	// 创建 getter 并读取 ref 对象的 value
    getter = () => source.value
    forceTrigger = isShallow(source)
  } else if (isReactive(source)) {
  	// 如果是 reactive 对象,直接返回 getter 函数,设置 deep 为 true 
    getter = () => source
    deep = 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)) {
          // traverse 就是一个递归,相当于深度监听
          return traverse(s)
        } else if (isFunction(s)) {
          return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
        } else {
          __DEV__ && warnInvalidSource(s)
        }
      })
  } else if (isFunction(source)) {
    if (cb) {
      // getter with cb,即提供了回调函数 cb
      getter = () =>
        callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
    } else {
      // no cb -> simple effect,即未提供回调函数 cb
      getter = () => {
        if (instance && instance.isUnmounted) {
          return
        }
        if (cleanup) {
          cleanup()
        }
        return callWithAsyncErrorHandling(
          source,
          instance,
          ErrorCodes.WATCH_CALLBACK,
          [onCleanup]
        )
      }
    }
  } else {
    getter = NOOP // 无效的监视源,设置 getter 为 NOOP 函数
    __DEV__ && warnInvalidSource(source)
  }

  // 如果开启了 2.x 兼容性选项且提供了回调函数 cb 且 deep 为 false,则添加兼容性支持
  if (__COMPAT__ && cb && !deep) {
    const baseGetter = getter
    getter = () => {
      const val = baseGetter()
      if (
        isArray(val) &&
        checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)
      ) {
        traverse(val)
      }
      return val
    }
  }

  // 如果设置了 deep 选项,对 getter 的返回值进行递归深度监听 traverse
  if (cb && deep) {
    const baseGetter = getter
    getter = () => traverse(baseGetter())
  }

  let cleanup: () => void
  let onCleanup: OnCleanup = (fn: () => void) => {
    cleanup = effect.onStop = () => {
      callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
    }
  }

  // 在服务器端渲染 (SSR) 模式下,不需要设置实际的 effect,它应该是无操作的,除非设置了 eager 或 sync flush 选项
  let ssrCleanup: (() => void)[] | undefined
  if (__SSR__ && isInSSRComponentSetup) {
    // 在 SSR 中,不会调用 invalidate 回调函数(+ runner 未设置)
    onCleanup = NOOP
    if (!cb) {
      getter() // 在 SSR 情况下立即获取数据
    } else if (immediate) {
      // 在 SSR 情况下,如果设置了 immediate 选项,则立即调用回调函数 cb
      callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
        getter(),
        isMultiSource ? [] : undefined,
        onCleanup
      ])
    }
    if (flush === 'sync') {
      // 在 SSR 情况下,如果设置了 sync flush,则获取 SSR 上下文,并将 cleanup 函数添加到 SSR 清理句柄中
      const ctx = useSSRContext()!
      ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = [])
    } else {
      return NOOP // 其他情况下,返回无操作函数
    }
  }

  // 初始化 oldValue,根据数据源是否为多源(数组)进行初始化
  let oldValue: any = isMultiSource
    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
    : INITIAL_WATCHER_VALUE

  // 定义要运行的任务(job)函数,根据 cb 是否存在执行不同逻辑
  const job: SchedulerJob = () => {
    if (!effect.active) {
      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)) ||
        (__COMPAT__ &&
          isArray(newValue) &&
          isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance))
      ) {
        // 在运行回调函数 cb 之前清理
        if (cleanup) {
          cleanup()
        }
        callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
          newValue,
          // 当首次变化时(INITIAL_WATCHER_VALUE 为 {})将旧值传递为 undefined
          oldValue === INITIAL_WATCHER_VALUE ? undefined : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE ? [] : oldValue, onCleanup
        ])
        // 彩蛋!!基础笔记中:`如果是引用类型(比如直接监听最外层 message)(此时已开启深度监听),则返回的新值和旧值是一样的(深层也是一样的,都是新值的数值)` 
        // 原因就在此:直接将引用(地址)赋值,深层也必定一样
        oldValue = newValue
      }
    } else {
      // watchEffect 情况
      effect.run()
    }
  }

  // 重要:将任务标记为 watcher 回调,以便调度器知道可以自行触发它 (#1727)
  job.allowRecurse = !!cb
  // 执行不同调度
  let scheduler: EffectScheduler

  // 根据 flush 选项设置调度器
  if (flush === 'sync') {
    scheduler = job as any // 直接调用任务函数
  } else if (flush === 'post') {
  	// queuePostRenderEffect 组件更新之后执行
    scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
  } else {
    // 默认情况下使用 'pre'
    job.pre = true
    if (instance) job.id = instance.uid
    scheduler = () => queueJob(job)
  }

  // 创建响应式 effect
  const effect = new ReactiveEffect(getter, scheduler)

  if (__DEV__) {
    effect.onTrack = onTrack
    effect.onTrigger = onTrigger
  }

  // 初始化运行任务
  if (cb) {
    if (immediate) {
      job() // 立即运行任务
    } else {
      oldValue = effect.run() // 初始化 oldValue
    }
  } else if (flush === 'post') {
    // 如果是 watchEffect,且 flush 为 'post',则将任务添加到 post-render 队列中执行
    queuePostRenderEffect(
      effect.run.bind(effect),
      instance && instance.suspense
    )
  } else {
    effect.run() // 初始化运行任务
  }

  // 定义停止监视的函数
  const unwatch = () => {
    effect.stop() // 停止 effect
    if (instance && instance.scope) {
      remove(instance.scope.effects!, effect) // 从组件实例中移除 effect
    }
  }

  // 在 SSR 情况下,将 cleanup 函数添加到清理句柄中
  if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)

  // 返回停止监视的函数
  return unwatch
}

上述代码实现了watch的核心逻辑。

它根据不同的数据源类型,设置不同的getter函数,然后在数据源发生变化时执行回调函数或效果函数。它还支持immediatedeepflush等选项,以及在服务器端渲染(SSR)环境下的特殊处理。

这段代码的核心是创建一个响应式effect,并在需要时执行回调函数或效果函数,以实现数据的监视和响应。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小秀_heo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值