vue3 watch源码解读

在 Vue 3 中,watch 是一个非常实用的 API,用于响应式地监听数据的变化并执行相应的回调。下面我们来详细解读 watch 的源码。

前置知识

在深入源码前,需要了解一些 Vue 3 中的核心概念:

  • 响应式系统:Vue 3 采用了 @vue/reactivity 包实现响应式,核心是 Proxy 对象来劫持数据的读写操作。
  • 副作用函数(Effect)effect 函数用于创建一个响应式的副作用,当依赖的数据发生变化时,副作用函数会重新执行。

源码位置与调用流程

watch 的源码位于 @vue/runtime-core 包中,核心函数是 doWatchwatch 函数只是对 doWatch 的简单封装:

import { effect, stop, isRef, isReactive, isFunction, isArray } from '@vue/reactivity'

export function watch(
  source: WatchSource | WatchSource[],
  cb: WatchCallback,
  options?: WatchOptions
): WatchStopHandle {
  return doWatch(source, cb, options)
}

doWatch 函数详细解读

1. 解析 source 参数

source 可以是多种类型,如 refreactive 对象、函数或数组,根据不同类型生成对应的 getter 函数:

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

if (isRef(source)) {
  getter = () => source.value
  forceTrigger = isShallow(source)
} else if (isReactive(source)) {
  getter = () => source
  deep = true
} else if (isArray(source)) {
  isMultiSource = true
  forceTrigger = source.some(isShallow)
  getter = () =>
    source.map(s => {
      if (isRef(s)) {
        return s.value
      } else if (isReactive(s)) {
        return traverse(s)
      } else if (isFunction(s)) {
        return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
      }
    })
} else if (isFunction(source)) {
  if (cb) {
    // getter with cb
    getter = () =>
      callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
  } else {
    // no cb -> simple effect
    getter = () => {
      if (instance && instance.isUnmounted) {
        return
      }
      if (cleanup) {
        cleanup()
      }
      return callWithErrorHandling(
        source,
        instance,
        ErrorCodes.WATCH_CALLBACK,
        [onInvalidate]
      )
    }
  }
} else {
  getter = NOOP
}
  • isRef:如果 sourcerefgetter 直接返回 source.value
  • isReactive:如果 source 是响应式对象,getter 返回该对象,并且将 deep 选项设为 true,表示深度监听。
  • isArray:如果 source 是数组,对数组中的每个元素进行处理,根据元素类型生成对应的返回值。
  • isFunction:如果 source 是函数,根据是否有回调函数 cb 进行不同处理。
2. 处理 deep 选项

如果 cb 存在且 deep 选项为 true,则对 getter 进行包装,使用 traverse 函数进行深度遍历:

if (cb && deep) {
  const baseGetter = getter
  getter = () => traverse(baseGetter())
}
3. 处理 onInvalidate

onInvalidate 用于注册一个清理函数,在副作用函数重新执行或停止时调用:

let cleanup: () => void
let onInvalidate: InvalidateCbRegistrator = (fn: () => void) => {
  cleanup = runner.options.onStop = () => {
    callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
  }
}
4. 初始化旧值
let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE
5. 定义 job 函数

job 函数是数据变化时执行的任务,根据是否有回调函数 cb 进行不同处理:

const job = () => {
  if (!runner.active) {
    return
  }
  if (cb) {
    // watch(source, cb)
    const newValue = runner()
    if (
      deep ||
      forceTrigger ||
      (isMultiSource
        ? (newValue as any[]).some((v, i) =>
            hasChanged(v, (oldValue as any[])[i])
          )
        : hasChanged(newValue, oldValue))
    ) {
      if (cleanup) {
        cleanup()
      }
      callWithErrorHandling(
        cb,
        instance,
        ErrorCodes.WATCH_CALLBACK,
        [newValue, oldValue, onInvalidate]
      )
      oldValue = newValue
    }
  } else {
    // watchEffect
    runner()
  }
}
6. 定义调度器 scheduler

根据 flush 选项决定调度方式:

let scheduler: (job: () => void) => void
if (flush === 'sync') {
  scheduler = job => job()
} else if (flush === 'post') {
  scheduler = job => queuePostRenderEffect(job, instance)
} else {
  // default: 'pre'
  scheduler = job => {
    if (!instance || instance.isMounted) {
      queuePreFlushCb(job)
    } else {
      // with 'pre' option, the first call must happen before
      // the component is mounted so it is called synchronously.
      job()
    }
  }
}
7. 创建副作用函数 runner

使用 effect 函数创建副作用函数,并传入 getter 和调度器 scheduler

const runner = effect(getter, {
  lazy: true,
  scheduler,
  onTrack,
  onTrigger
})
8. 初始调用

根据是否有回调函数 cbimmediate 选项决定是否立即执行 job 函数:

if (cb) {
  if (immediate) {
    job()
  } else {
    oldValue = runner()
  }
} else {
  // watchEffect
  runner()
}
9. 返回停止监听的函数
const stop = () => {
  stop(runner)
  if (cleanup) {
    cleanup()
  }
}

return stop

总结

Vue 3 的 watch 源码通过对不同类型的 source 进行解析,结合 effect 函数和调度器实现了数据的监听和回调执行。主要步骤包括:

  1. 解析 source 生成 getter 函数。
  2. 处理 deep 选项。
  3. 定义 job 函数和调度器。
  4. 创建副作用函数 runner
  5. 根据选项决定是否立即执行。
  6. 返回停止监听的函数。

通过这种方式,watch 可以灵活地监听不同类型的数据变化,并在合适的时机执行回调函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值