VUE 3.0源码之watch和watchEffect

本文深入探讨Vue 3.0中watch和watchEffect的区别与相似之处,分析它们在数据监听和副作用处理上的差异,包括参数数量、功能特性以及如何停止监听。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.watch和watchEffect

这两都是直接调用doWatch方法,唯一区别就是第二个参数不同。

export function watchEffect(
  effect: WatchEffect,
  options?: WatchOptionsBase
): WatchStopHandle {
  return doWatch(effect, null, options)
}
export function watch<T = any>(
  source: WatchSource<T> | WatchSource<T>[],
  cb: WatchCallback<T>,
  options?: WatchOptions
): WatchStopHandle {
  // 省略参数校验
  return doWatch(source, cb, options)
}

2.doWatch

function doWatch(
  source: WatchSource | WatchSource[] | WatchEffect,
  cb: WatchCallback | null,
  { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
): WatchStopHandle {
  // 当前组件实例
  const instance = currentInstance
  // 将不同类型的监视对象封装成函数,并在函数内部触发相关get,类似计算属性
  let getter: () => any
  if (isArray(source)) {
    getter = () =>
      source.map(s => {
        if (isRef(s)) {
          return s.value
        } else if (isReactive(s)) {
          // 触发proxy所有不同层级的get
          return traverse(s)
        } else if (isFunction(s)) {
          // 直接执行,但会捕获错误
          return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
        } else {
          __DEV__ && warnInvalidSource(s)
        }
      })
  } else if (isRef(source)) {
    getter = () => source.value
  } else if (isReactive(source)) {
    // 强制深层监听
    getter = () => source
    deep = true
  } else if (isFunction(source)) {
    if (cb) {
      getter = () =>
        callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
    } else {
      getter = () => {
        if (instance && instance.isUnmounted) {
          return
        }
        // 在每次触发getter时,调用onInvalidate传入的函数
        if (cleanup) {
          cleanup()
        }
        return callWithErrorHandling(
          source,
          instance,
          ErrorCodes.WATCH_CALLBACK,
          [onInvalidate]
        )
      }
    }
  } else {
    getter = NOOP
    __DEV__ && warnInvalidSource(source)
  }
  
  if (cb && deep) {
    // 如果是watch且是deep则递归触发get
    const baseGetter = getter
    getter = () => traverse(baseGetter())
  }
  
  let cleanup: () => void
  const onInvalidate: InvalidateCbRegistrator = (fn: () => void) => {
    // 1、副作用即将重新执行时
    // 2、侦听器被停止 (如果在 setup() 或生命周期钩子函数中使用了 watchEffect,则在组件卸载时)
    cleanup = runner.options.onStop = () => {
      callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
    }
  }
  // 如果是ssr且在node则不做监听,只执行一遍
  if (__NODE_JS__ && isInSSRComponentSetup) {
    if (!cb) {
      getter()
    } else if (immediate) {
      callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
        getter(),
        undefined,
        onInvalidate
      ])
    }
    return NOOP
  }
  
  let oldValue = isArray(source) ? [] : INITIAL_WATCHER_VALUE
  const applyCb = cb
    ? () => {
        if (instance && instance.isUnmounted) {
          return
        }
        const newValue = runner()
        if (deep || hasChanged(newValue, oldValue)) {
          if (cleanup) {
            cleanup()
          }
          callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
            newValue,
            // 第一次触发传undefined做旧值
            oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
            onInvalidate
          ])
          oldValue = newValue
        }
      }
    : void 0
    
  let scheduler: (job: () => any) => void
  if (flush === 'sync') {
    // 会在内部响应式更新后直接触发
    // const invoke = (fn: Function) => fn()
    scheduler = invoke
  } else if (flush === 'pre') {
    // 组件更新前执行
    scheduler = job => {
      if (!instance || instance.isMounted) {
        // 异步队列
        queueJob(job)
      } else {
        // pre的时候,第一次调用要在mounted前,所以直接同步调用
        job()
      }
    }
  } else {
    // 组件更新后运行
    scheduler = job => queuePostRenderEffect(job, instance && instance.suspense)
  }
  
  // 生成一个effect用于依赖收集和注册
  const runner = effect(getter, {
    lazy: true,
    computed: true,
    onTrack,
    onTrigger,
    // 如果是watch则运行applyCb,否则运行runner
    scheduler: applyCb ? () => scheduler(applyCb) : scheduler
  })
  
  // 将副作用添加到当前实例的副作用列表中
  recordInstanceBoundEffect(runner)
  
  if (applyCb) {
    if (immediate) {
      applyCb()
    } else {
      oldValue = runner()
    }
  } else {
    runner()
  }
  
  // 返回一个函数用于停用该副作用
  return () => {
    stop(runner)
    if (instance) {
      remove(instance.effects!, runner)
    }
  }
}

2、总结

watch和watchEffect的区别
  • watchEffect和computed比较像,都是通过执行副作用函数获取要监听的数据源,而watch是通过第一参数获取要监听的数据源
  • watch的参数有3个(数据源,副作用函数,配置),watchEffect只有两个(副作用函数,配置)
  • watch的副作用函数接收的参数比watchEffect多两个(新旧值)
  • deep和immediate只对watch有用
watch和watchEffect的相同点
  • watch 与 watchEffect共享停止侦听,清除副作用 (相应地 onInvalidate 会作为回调的第三个参数传入)、副作用刷新时机和侦听器调试行为。
  • 都能通过返回的停止函数停止监听
  • 都有onInvalidate传参
  • 相同的副作用刷新时机
  • 都有flush(刷新时机)、onTrack(被依赖收集时)、onTrigger(被依赖触发时)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值