vue3 learning source code notes (newbie entry series) ------how watch watchEffect works

background

When we need to perform related operations after changing one or some responsive data during development, we can use
watch and watchEffect api to build a new effect to complete the responsive operation.

watch, the essence of watchEffect

The core code is located in runtime-core/src/apiwatch

Input parameters and return values ​​of watch

Entering
// 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:调整回调函数的刷新时机 后面 会详细分析
Return results
export type WatchStopHandle = () => void
本质返回了 一个 可以销毁监听effect 的 函数 后面会给出分析

doWatch

Both watch and watchEffect call this function, but the input parameters are different. watch has an additional callback function.

The first step is to create a getter for dependency collection

For watch, source is a reactive data and for watchEffect, source is a function.
Since listening sources will have different types of dowatch, the first step is to standardize these different types of sources.

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())
  }
  
  // ...
}

Conclusion: Different types of sources will be converted into a getter function.
The getter of watch can be regarded as a function that accesses reactive data.
The getter of watchEffect can be regarded as a business execution function that uses reactive data.

Create a job to process the dispatch update operation that occurs after responsive data
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()
    }
  }

Create ReactiveEffect based on getter and job


// 先根据 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 默认 和 组件更新处于同一个微任务队列中,并先于组件更新操作 

Finally, return a function that eliminates the side effects of effect

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

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

Expand

When does watch and watchEffect dependency collection occur?

// 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()
  }

Summary:
watch and watchEffect both start with the first dependency collection that occurs based on the getter. Subsequent dependency collection and comparison will be different. watch uses the callback function registered by cb, while watchEffect still uses the getter.

Full text summary

The essence of watch and watchEffect is to create a new ReactiveEffect to manage responsive operations and trigger different stages of job updates based on the flush status.

Guess you like

Origin blog.csdn.net/weixin_45485922/article/details/133383563