目录

vue3-watchEffect源码解读

vue3 watchEffect源码解读

1. watchEffect 函数入口

watchEffect 函数定义在 @vue/runtime-core 包中,以下是简化后的核心代码:

import { effect, ReactiveEffect } from '@vue/reactivity'

export function watchEffect(
  effectFn: (onInvalidate: InvalidateCbRegistrator) => void,
  options?: WatchEffectOptions
): StopHandle {
  return doWatch(effectFn, null, options)
}
  • watchEffect 接收一个副作用函数 effectFn 和可选的配置项 options
  • 它实际上调用了 doWatch 函数,将 effectFn 作为第一个参数传入,第二个参数为 null ,这与 watch 的区别在于, watch 通常会传入要监听的数据源,而 watchEffect 会自动收集副作用函数中使用的响应式数据作为依赖。

2. doWatch 函数核心逻辑

function doWatch(
  source: WatchSource | WatchSource[] | WatchEffect | object,
  cb: WatchCallback | null,
  { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
): StopHandle {
  // ...
  let getter: () => any
  let forceTrigger = false
  let isMultiSource = false

  if (isFunction(source)) {
    if (cb) {
      // watch(source, cb)
      getter = () => source()
    } else {
      // watchEffect(source)
      getter = () => {
        if (cleanup) {
          cleanup()
        }
        return callWithErrorHandling(source, instance, ErrorCodes.WATCH_CALLBACK, [
          onInvalidate
        ])
      }
    }
  } 
  // ...

  let cleanup: () => void
  let onInvalidate: InvalidateCbRegistrator = (fn: () => void) => {
    cleanup = effect.onStop = () => {
      callWithAsyncErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
    }
  }

  const effect = new ReactiveEffect(getter, () => {
    if (!instance || instance.isUnmounted) {
      return
    }
    if (cb) {
      // watch(source, cb)
      // ...
    } else {
      // watchEffect
      schedulePostFlushCb(run)
    }
  })

  effect.onTrack = onTrack
  effect.onTrigger = onTrigger

  if (immediate) {
    if (cb) {
      callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
        getter(),
        undefined,
        onInvalidate
      ])
    } else {
      run()
    }
  } else {
    run()
  }

  return () => {
    effect.stop()
    if (instance && instance.scope) {
      remove(instance.scope.effects!, effect)
    }
  }
}
关键步骤分析:
  • getter 函数的创建
    • cbnull 时,说明是 watchEffect 调用。 getter 函数会先检查是否有清理函数 cleanup ,如果有则执行它,然后调用传入的副作用函数 source ,并将 onInvalidate 作为参数传入。
  • onInvalidate 函数
    • 用于注册清理函数。当副作用函数重新执行或停止时,清理函数会被调用。例如,在副作用函数中发起异步请求,当请求还未完成时副作用函数就需要重新执行,这时可以使用 onInvalidate 来取消之前的请求。
  • ReactiveEffect 实例的创建
    • ReactiveEffect 是 Vue 响应式系统中用于管理副作用函数和依赖关系的核心类。它接收 getter 函数和一个调度函数作为参数。
    • 调度函数会在依赖项发生变化时被调用。对于 watchEffect ,它会将 run 函数调度到微任务队列中执行,确保在 DOM 更新后再执行副作用函数。
  • 立即执行
    • 如果 immediatetrue ,则会立即执行副作用函数。对于 watchEffect ,直接调用 run 函数。
  • 返回停止函数
    • doWatch 函数返回一个停止函数,调用该函数可以停止 watchEffect 的监听,即调用 effect.stop() 方法停止 ReactiveEffect 实例的运行。

3. ReactiveEffect 类的作用

export class ReactiveEffect<T = any> {
  active = true
  deps: Dep[] = []
  // ...

  constructor(
    public fn: () => T,
    public scheduler: EffectScheduler | null = null
  ) {}

  run() {
    if (!this.active) {
      return this.fn()
    }
    try {
      activeEffect = this
      // 清理上一次的依赖
      cleanupEffect(this)
      // 开始收集依赖
      trackOpBit = 1 << ++effectTrackDepth
      return this.fn()
    } finally {
      // 恢复之前的依赖收集状态
      trackOpBit = 1 << --effectTrackDepth
      activeEffect = undefined
    }
  }

  stop() {
    if (this.active) {
      cleanupEffect(this)
      if (this.onStop) {
        this.onStop()
      }
      this.active = false
    }
  }
}
  • ReactiveEffect 类的 run 函数会在执行副作用函数之前,将当前的 ReactiveEffect 实例设置为全局的 activeEffect ,以便在副作用函数内部访问响应式数据时进行依赖收集。
  • 在执行完副作用函数后,会恢复之前的依赖收集状态。
  • stop 函数用于停止 ReactiveEffect 实例的运行,清理依赖关系,并执行可能存在的停止回调函数 onStop

总结

watchEffect 的核心原理是通过 ReactiveEffect 类来管理副作用函数和依赖关系。在副作用函数执行过程中,自动收集所使用的响应式数据作为依赖。当这些依赖发生变化时,调度函数会被触发,重新执行副作用函数。同时, onInvalidate 提供了清理机制,用于处理副作用函数重新执行或停止时的清理工作。