Vue watch与watchEffect

2,757 阅读4分钟

watchEffect

它立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。

let count = ref(1)
const stop = watchEffect(() => console.log(count.value))
// ->log 1

let add = () => count.value++
// ->log 2

// 显式调用返回值以停止侦听
stop()

副作用刷新时机

presyncpost
更新时机组件更新前执行强制效果始终同步触发组件更新后执行

作用和 computed 差不多,可以监听使用的响应式追踪其依赖变化时执行副作用函数。但 watchEffect 没有缓存,返回值也是用于停止侦听。

watch

watch 需要侦听特定的数据源,并在回调函数中执行副作用。默认情况下,它也是惰性的,即只有当被侦听的源发生变化时才执行回调。

参数

getter 函数

const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)

侦听ref

const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})

侦听reative

const count = reative(0)
watch(count, (count, prevCount) => {
  /* ... */
})

侦听多个数据源

const firstName = ref('')
const lastName = ref('')

watch([firstName, lastName], (newValues, prevValues) => {
  console.log(newValues, prevValues)
})

侦听响应式对象

const numbers = reactive([1, 2, 3, 4])

watch(
  () => [...numbers],
  (numbers, prevNumbers) => {
    console.log(numbers, prevNumbers)
  }
)

numbers.push(5)

与 watchEffect 共享的行为

1,停止侦听
let stop1 = watchEffect(() => ...)
let stop2 = watch(count, (count, prevCount) => { ... }) 

stop1()
stop2()

2,清除副作用
onInvalidate 作为一个实参传入
watchEffect( onInvalidate => onInvalidate( ()=>{...} ) )
watch(count, (count, prevCount,onInvalidate) => { ...; onInvalidate(()=>...) }) 
onInvalidate  1,副作用即将重新执行时执行 2,watch失效回调会被触发

3,副作用刷新时机
flush:  pre | sync | post

4,侦听器调试
onTrack 和 onTrigger 选项可用于调试侦听器的行为。
onTrack 将在响应式 property 或 ref 作为依赖项被追踪时被调用
onTrigger 将在依赖项变更导致副作用被触发时被调用

源码实现

watchEffect

export function watchEffect(
  effect,options
) {
  return doWatch(effect, null, options)
}

watch

export function watch(
  source,cb,options
) {
  // 执行的不为函数时,直接报错
  if (__DEV__ && !isFunction(cb)) {
    warn(...)
  }
  return doWatch(source, cb, options)
}

可以看到 watchEffect 和 watch 最大的区别在于形参的传递不同

doWatch

function doWatch(
  source: WatchSource | WatchSource[] | WatchEffect | object,
  cb: WatchCallback | null,
  { immediate, deep, flush, onTrack, onTrigger }
) {
  
...

}

警告

  // watchEffect 不能设置 immediate 和 deep
  if (__DEV__ && !cb) {
    if (immediate !== undefined) {
      warn(...)
    }
    if (deep !== undefined) {
      warn(...)
    }
  }

搜集数据

  // 初始化数据
  // 提示警告使用
  const warnInvalidSource = (s: unknown) => {
    warn(...)
  }

  const instance = null
  // 返回原数据
  let getter: () => any
  // 是否深层遍历
  let forceTrigger = false
  let isMultiSource = false

  if (isRef(source)) {
    // 监听 ref 
    getter = () => source.value
    forceTrigger = !!source._shallow
  } else if (isReactive(source)) {
    // 监听 reactive
    getter = () => source
    deep = true
  } else if (isArray(source)) {
    // 多个数据源
    isMultiSource = true
    forceTrigger = source.some(isReactive)
    getter = () =>
      source.map(s => {
        if (isRef(s)) {
          return s.value
        } else if (isReactive(s)) {
          // traverse遍历 reactive 下的所有元素
          return traverse(s)
        } else if (isFunction(s)) {
          // 返回执行的结果
          return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
        } else {
          // 报异常,只能监听 响应式变量
          __DEV__ && warnInvalidSource(s)
        }
      })
  } else if (isFunction(source)) {
    if (cb) {
      // 一个watch
      // 返回 source 的执行结果
      getter = () =>
        callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
    } else {
      // 一个watchEffect
      getter = () => {
        if (instance && instance.isUnmounted) {
          return
        }
        if (cleanup) {
          cleanup()
        }
        // 返回 watchEffect 的执行结果,传入实参 onInvalidate
        return callWithAsyncErrorHandling(
          source,
          instance,
          ErrorCodes.WATCH_CALLBACK,
          [onInvalidate]
        )
      }
    }
  } else {
    // 非 响应式变量 直接报警告
    getter = NOOP
    __DEV__ && warnInvalidSource(source)
  }
  
  // watch 且 deep 为true,使用 traverse 深层遍历让其所有 响应式元素 搜集依赖
  if (cb && deep) {
    const baseGetter = getter
    getter = () => traverse(baseGetter())
  }
  • 监听的数据源必须是 响应式函数
  • 监听的数据源是 reactive 会自动 deep 深层遍历,监听响应
  • traverse 会遍历 reactive 下所有响应式元素,搜集依赖

执行

// 清除副作用执行
let cleanup: () => void
let onInvalidate: InvalidateCbRegistrator = (fn: () => void) => {
  cleanup = effect.onStop = () => {
    callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
  }
}

// 初始化老值
let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE
// 执行 watch 或 watchEffect
  const job = () => {
    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))
      ) {
        // 清除副作用执行
        if (cleanup) {
          cleanup()
        }
        // 执行 cb 传入形参 newValue oldVaue onInvalidate
        callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
          newValue,
          // 如果 oldValue 是初始化的 {} ,传入 undefined。第一次执行会出现这种情况
          oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
          onInvalidate
        ])
        oldValue = newValue
      }
    } else {
      // watchEffect
      effect.run()
    }
  }
执行 watch
  • deep 设置了{ deep : true } 或 reactive 下执行
  • forceTrigger 1,多个数据源中有 reactive 2,ref
  • 对比新老数据是否变更,Object.is 没有深层对比

flush

  let scheduler: EffectScheduler
  if (flush === 'sync') {
    // 同步执行
    scheduler = job
  } else if (flush === 'post') {
    // 进入异步队列,组件更新后执行
    scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
  } else {
    // 进入异步队列,组件更新前执行
    scheduler = () => {
      if (!instance || instance.isMounted) {
        queuePreFlushCb(job)
      } else {
        // 如果组件还没挂载,则同步执行确保回调函数在组件挂载之前执行。
        job()
      }
    }
  }

  // 设置 watch 的effect,之后触发getter中的元素被修改就触发 scheduler
  const effect = new ReactiveEffect(getter, scheduler)
  
  // 设置了 onTrack 和 onTrigger 的话
  if (__DEV__) {
    effect.onTrack = onTrack
    effect.onTrigger = onTrigger
  }
  
  // 执行
  if (cb) {
    // immediate 同步执行一次 watch
    if (immediate) {
      job()
    } else {
      // 把 oldValue 设置成当前值
      oldValue = effect.run()
    }
  } else if (flush === 'post') {
    queuePostRenderEffect(
      effect.run.bind(effect),
      instance && instance.suspense
    )
  } else {
    effect.run()
  }

  return () => {
    // watch 和 watchEffect 都会接收到一个消除 effect 的函数 Stop
    // 执行一次 onInvalidate
    effect.stop()
    // 删除当前作用域下的 effect
    if (instance && instance.scope) {
      remove(instance.scope.effects!, effect)
    }
  }
watchwatchEffect
执行惰性执行,除非immediate:true初始化时会运行一次
监听监听指定的变量监听副作用函数内的使用的响应式变量
回调参数NewValue,OldValue,onInvalidateonInvalidate
依赖搜集source中指定的数据源,如果是 reactive 自动使用 deep执行 fn 搜集其中使用的 响应式变量 依赖

依赖搜集的方式不同,使得 watch 可以在监听的数据源变化后执行。而 watchEffect 必须先执行一次收集依赖。