Vue3: 使用 watch

105 阅读3分钟

[vue3源码](GitHub - vuejs/core: 🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.)

watch

  1. 监听响应式数据变化
import { ref, watch } from 'vue'

const data = ref('')

watch(data, (newData, oldData) => {
  console.log(newData, oldData);
})
  <div>
    <input v-model="data" type="text"><br />
    {{ data }}
  </div>
  1. 监听对象

写法一: watch加第三个参数传一个对象{},加deeptrue,表示深度监听

import { ref, watch } from 'vue'

const obj = ref({
  name: {
    suit: {
      nuwa: 7800
    }
  }
})

watch(obj, (newObj, oldObj) => {
  console.log(newObj, oldObj);
}, { // <-------------------------- watch 的第三个参数
  deep: true
})
  <div>
    <input type="text" v-model="obj.name.suit.nuwa">
    {{ obj.name.suit.nuwa }}
  </div>

写法二: 使用reactive,reactive在源码中已设置deeptrue,无需再手动设置

import { ref, watch, reactive } from 'vue'

const obj = reactive({ // <----------
  name: {
    suit: {
      nuwa: 7800
    }
  }
})

watch(obj, (newObj, oldObj) => {
  console.log(newObj, oldObj);
})
  1. watch的第三个参数
watch(obj, (newObj, oldObj) => {
  console.log(newObj, oldObj);
}, {
  deep: true, // 深度监听
  immediate: true,// 立即执行一次
  /* 
    post: 组件更新后执行
    pre: 组件更新前执行
    sync: 同步执行
  */
  flush: "pre" // post pre sync
})

源码

位置: \core-main\packages\runtime-core\src\apiWatch.ts

export function watch<T = any, Immediate extends Readonly<boolean> = false>(
  source: T | WatchSource<T>,
  cb: any,
  options?: WatchOptions<Immediate>
): WatchStopHandle {
  if (__DEV__ && !isFunction(cb)) {
    warn(
      `\`watch(fn, options?)\` signature has been moved to a separate API. ` +
        `Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +
        `supports \`watch(source, cb, options?) signature.`
    )
  }
  // watch(obj,(newObj,oldObj)=>{...},{deep:true,...})
  return doWatch(source as any, cb, options)
}

doWatch函数

function doWatch(
  source: WatchSource | WatchSource[] | WatchEffect | object,
  cb: WatchCallback | null,
  { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
): WatchStopHandle {
  if (__DEV__ && !cb) {
    if (immediate !== undefined) {
      warn(
        `watch() "immediate" option is only respected when using the ` +
          `watch(source, callback, options?) signature.`
      )
    }
    if (deep !== undefined) {
      warn(
        `watch() "deep" option is only respected when using the ` +
          `watch(source, callback, options?) signature.`
      )
    }
  }

  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 =
    getCurrentScope() === currentInstance?.scope ? currentInstance : null
  // const instance = currentInstance
  let getter: () => any
  let forceTrigger = false
  let isMultiSource = false
    
  if (isRef(source)) {
  // 是 ref 对象, 直接赋值给 getter 
    getter = () => source.value
    forceTrigger = isShallow(source)
  } else if (isReactive(source)) {
  // 是 reactive 对象, 把这个对象直接赋值给 getter
  // 并且设置 deep 为 true
    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)) {
        // 是 ref 对象, 还是直接返回 ref 对象的值
          return s.value
        } else if (isReactive(s)) {
        // 是 reactive 对象, 调用 traverse 函数
        // traverse 递归函数
          return traverse(s)
        } else if (isFunction(s)) {
        // 是函数,再进行别的处理加工
          return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
        } else {
          __DEV__ && warnInvalidSource(s)
        }
      })
  } else if (isFunction(source)) {
  // 第一个参数传的是函数 watch(()=>{},(newVal,oldVal)=>{...},{...})
    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 callWithAsyncErrorHandling(
          source,
          instance,
          ErrorCodes.WATCH_CALLBACK,
          [onCleanup]
        )
      }
    }
  } else {
    getter = NOOP
    __DEV__ && warnInvalidSource(source)
  }

  // 2.x array mutation watch compat
  if (__COMPAT__ && cb && !deep) {
    const baseGetter = getter
    getter = () => {
      const val = baseGetter()
      if (
        isArray(val) &&
        checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)
      ) {
        traverse(val)
      }
      return val
    }
  }

  if (cb && deep) {
  // 函数存在且deep属性为true,继续调用 traverse 递归函数
    const baseGetter = getter
    getter = () => traverse(baseGetter())
  }

  let cleanup: () => void
  let onCleanup: OnCleanup = (fn: () => void) => {
    cleanup = effect.onStop = () => {
      callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
    }
  }

  // in SSR there is no need to setup an actual effect, and it should be noop
  // unless it's eager or sync flush
  let ssrCleanup: (() => void)[] | undefined
  if (__SSR__ && isInSSRComponentSetup) {
    // we will also not call the invalidate callback (+ runner is not set up)
    onCleanup = NOOP
    if (!cb) {
      getter()
    } else if (immediate) {
      callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
        getter(),
        isMultiSource ? [] : undefined,
        onCleanup
      ])
    }
    if (flush === 'sync') {
      const ctx = useSSRContext() as SSRContext
      ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = [])
    } else {
      return NOOP
    }
  }

  // 初始化旧值
  let oldValue: any = isMultiSource
    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
    : INITIAL_WATCHER_VALUE
  const job: SchedulerJob = () => {
    if (!effect.active) {
      return
    }
    if (cb) {
      // watch(source, cb)
      // 获取新值
      const newValue = effect.run()
      if (
        deep ||
        forceTrigger ||
        (isMultiSource
          ? (newValue as any[]).some((v, i) =>
              hasChanged(v, (oldValue as any[])[i])
            )
          : hasChanged(newValue, oldValue)) ||
        (__COMPAT__ &&
          isArray(newValue) &&
          isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance))
      ) {
        // cleanup before running cb again
        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 的指针是一样的, 是 {}
              oldValue(旧值) 是 {}, 就会返回 undefined
          */
          oldValue === INITIAL_WATCHER_VALUE
            ? undefined
            : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
            ? []
            : oldValue,
          onCleanup
        ])
        // 直接赋值,引用地址相同,所以在页面中看到的新值与旧值一样
        oldValue = newValue
      }
    } else {
      // watchEffect
      effect.run()
    }
  }

  // important: mark the job as a watcher callback so that scheduler knows
  // it is allowed to self-trigger (#1727)
  job.allowRecurse = !!cb

  let scheduler: EffectScheduler
  if (flush === 'sync') {
    // 同步执行
    scheduler = job as any // the scheduler function gets called directly
  } else if (flush === 'post') {
    // 组件更新后执行,把上面的 job 函数 传给了 queuePostRenderEffect 函数
    scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
  } else {
    // default: 'pre'
    job.pre = true
    if (instance) job.id = instance.uid
    scheduler = () => queueJob(job)
  }
  // 收集依赖完毕,此时 job 函数还不会执行
  const effect = new ReactiveEffect(getter, scheduler)

  if (__DEV__) {
    effect.onTrack = onTrack
    effect.onTrigger = onTrigger
  }

  // initial run
  if (cb) {
    if (immediate) {
    // 当 immediate 属性为 true 时,才会执行 job 函数
      job()
    } else {
      // 没有设置 immediate, 那么就给旧值初始化
      oldValue = effect.run()
    }
  } else if (flush === 'post') {
    // 组件更新后才会监听
    queuePostRenderEffect(
      effect.run.bind(effect),
      instance && instance.suspense
    )
  } else {
    effect.run()
  }

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

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