vue3学习源码笔记(小白入门系列)------watch watchEffect是如何工作的

目录

  • 背景
    • watch,watchEffect的本质
      • watch 的 入参和返回值
        • 入参
        • 返回结果
      • doWatch
        • 第一步 创建一个 getter 用于做依赖收集
        • 创建 一个 job 处理 响应式数据 后发生的派发更新操作
      • 根据getter 和 job 创建 ReactiveEffect
      • 最后返回一个 消除 effect 副作用的 函数
      • 扩展
      • watch 和 watchEffect 什么时候发生的依赖收集
    • 全文总结

背景

当开发中 我们需要对某个或一些响应式数据改变后,做相关操作,这时候可以使用
watch ,watchEffect api 构建新的 effect 完成 响应式操作。

watch,watchEffect的本质

核心代码 位于 runtime-core/src/apiwatch

watch 的 入参和返回值

入参
// watch 的类型定义 
export function watch<T, Immediate extends Readonly<boolean> = false>(
  source: WatchSource<T>,
  cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
  options?: WatchOptions<Immediate>
): WatchStopHandle
///
// overload: watching reactive object w/ cb
export function watch<
  T extends object,
  Immediate extends Readonly<boolean> = false
>(
  source: T,
  cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
  options?: WatchOptions<Immediate>
): WatchStopHandle
// 三个入参
export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)

1. source 监听源 可以是 Ref ComputedRef 或者一个函数 或者 object
export type WatchCallback<V = any, OV = any> = (
  value: V,
  oldValue: OV,
  onCleanup: OnCleanup
) => any
2. cb 一个回调函数 会在监听源发生改变后 执行 会有 新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用
export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
  immediate?: Immediate
  deep?: boolean
}

export interface WatchOptionsBase extends DebuggerOptions {
  flush?: 'pre' | 'post' | 'sync'
}

export interface DebuggerOptions {
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}
3.options 5个参数 其中 onTrack/onTrigger 只会在开发环境生效 便于开发人员调试。
immediate:在侦听器创建时立即触发回调。第一次调用时旧值是 undefined。
deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。
flush:调整回调函数的刷新时机 后面 会详细分析
返回结果
export type WatchStopHandle = () => void
本质返回了 一个 可以销毁监听effect 的 函数 后面会给出分析

doWatch

watch 和 watchEffect 都是调用的这个函数 只是入参不同 watch 多了一个 回调函数

第一步 创建一个 getter 用于做依赖收集

对于 watch 来说 source 是一个 响应式数据 而对 watchEffect 来说 source是一个 函数。
由于 监听源source 会有不同的类型 dowatch 第一步就是去 标准化这些不同类型的source

function doWatch(source, cb, { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ) {
  // ...
  // source 不合法的时候警告函数
  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 = currentInstance
  let getter
  let forceTrigger = false
  let isMultiSource = false

  // 判断是不是 ref 类型
  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(s => isReactive(s) || isShallow(s))
    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 {
          __DEV__ && warnInvalidSource(s)
        }
      })
  }
  // 判断是不是函数类型
  else if (isFunction(source)) {
    if (cb) {
      // getter with cb
      getter = () =>
        callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
    } else {
      // 如果只有一个函数作为source 入参,则执行 watchEffect 的逻辑
      // ...
      getter = () => {
        if (instance && instance.isUnmounted) {
          return
        }
        // 用于清理 effect.stop() 执行完的 副作用 用于清理定时器和取消事件订阅等
        // watchEffect 有个 cleanup 的入参函数 就是用来执行 effect.stop 的回调的
        if (cleanup) {
          cleanup()
        }
        return callWithAsyncErrorHandling(
          source,
          instance,
          ErrorCodes.WATCH_CALLBACK,
          [onCleanup]
        )
      }
    }
  }
  // 都不符合,则告警
  else {
    getter = NOOP
    __DEV__ && warnInvalidSource(source)
  }

  // 深度监听
  if (cb && deep) {
    const baseGetter = getter
    getter = () => traverse(baseGetter())
  }
  
  // ...
}

结论: 不同类型的source 都会被转化成一个 getter 函数
watch 的 getter 可以看作是一个 访问 响应式数据的 函数
watchEffect 的 getter 可以看作是 一个使用了 响应式数据的 业务执行函数。

创建 一个 job 处理 响应式数据 后发生的派发更新操作
const job: SchedulerJob = () => {
    if (!effect.active) {
      return
    }
    if (cb) {
      // 有cb时 属于watch,每次执行 cb 是 会先计算拿到 最新的 getter 返回的值,并 进行下一次的依赖收集 
      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))
      ) {
        // cleanup before running cb again
        // 注意 watch cb 第三个参数即是 cleanup 他执行的时机 也是在effect 被销毁的时候
        if (cleanup) {
          cleanup()
        }
        callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
          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,
          onCleanup
        ])
        oldValue = newValue
      }
    } else {
      // watchEffect 每次都会重新执行 getter 
      effect.run()
    }
  }

根据getter 和 job 创建 ReactiveEffect


// 先根据 job 和传入的 flush 创建 scheduler 
// 会在响应式数据发生改变 派发更新的时候 执行 effect.shduler 时触发
let scheduler: EffectScheduler
  if (flush === 'sync') {
   // 同步执行
    scheduler = job as any // the scheduler function gets called directly
  } else if (flush === 'post') {
  // 创建的任务 会被放在 pendingPostFlushCbs 队列里面 就会比 queue 队列 执行时  机要晚 也就是 dom更新后才执行
    scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
  } else {
    // default: 'pre'
    // job 上带有 pre 会在执行 flushJobs 时 排序到前面 会先于组件更新执行回调
    job.pre = true
    if (instance) job.id = instance.uid
    scheduler = () => queueJob(job)
  }

  const effect = new ReactiveEffect(getter, scheduler)
小结 创建watch 的 options 中的 flush参数的三个值的含义 表示 watch 回调执行的时机不同

sync 表示 在 响应式 派发更新的时候直接就执行了 
post 表示在 组件更新微任务完成后执行的
pre 默认 和 组件更新处于同一个微任务队列中,并先于组件更新操作 

最后返回一个 消除 effect 副作用的 函数

 const unwatch = () => {
    effect.stop()
    if (instance && instance.scope) {
      remove(instance.scope.effects!, effect)
    }
  }

  if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)
  return unwatch

扩展

watch 和 watchEffect 什么时候发生的依赖收集

// initial run
  if (cb) {
   // 表示是 watch 逻辑
    if (immediate) {
    // 立即执行回调 job job 其实是调用 effect.run() 也就是 source 转化的 getter (一个 获取响应式数据的函数),就发生了依赖收集。
      job()
    } else {
      oldValue = effect.run()
    }
  } else if (flush === 'post') {
    queuePostRenderEffect(
      effect.run.bind(effect),
      instance && instance.suspense
    )
  } else {
  // 是watcheffect 逻辑 也是执行 getter 
    effect.run()
  }

总结:
watch 和 watchEffect 最开始 都是根据 getter 发生的首次依赖收集。后续的依赖收集比对 会有所不同 watch 走的是 cb 注册的回调函数 ,而 watchEffect 则还是 getter。

全文总结

watch 和 watchEffect 本质都是创建了一个新的 ReactiveEffect 来管理响应式等操作,根据 flush 的状态触发 job 的不同阶段更新。

你可能感兴趣的:(学习,笔记)