• vue3学习源码笔记(小白入门系列)------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:调整回调函数的刷新时机 后面 会详细分析
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    返回结果
    export type WatchStopHandle = () => void
    本质返回了 一个 可以销毁监听effect 的 函数 后面会给出分析
    
    • 1
    • 2

    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())
      }
      
      // ...
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    结论: 不同类型的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()
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    根据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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    小结 创建watch 的 options 中的 flush参数的三个值的含义 表示 watch 回调执行的时机不同
    
    sync 表示 在 响应式 派发更新的时候直接就执行了 
    post 表示在 组件更新微任务完成后执行的
    pre 默认 和 组件更新处于同一个微任务队列中,并先于组件更新操作 
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

     const unwatch = () => {
        effect.stop()
        if (instance && instance.scope) {
          remove(instance.scope.effects!, effect)
        }
      }
    
      if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)
      return unwatch
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    扩展

    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()
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

    全文总结

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

  • 相关阅读:
    bestphp‘s revenge/ 安洵杯Babyphp(phpsession题目)
    【引语练习题】关键词替换
    HTML1:html基础
    软件开发和测试
    《六顶思考帽》——产品脑暴会议也许可以这样玩
    [剑指 Offer 06]从尾到头打印链表
    C语言——二周目——输入输出辨析
    uniapp获取一周日期和星期
    【无标题】
    8张图解java
  • 原文地址:https://blog.csdn.net/weixin_45485922/article/details/133383563