Skip to content

监听器

Vue3中watch是一个钩子函数,并且新增了一个WatchEffect

1.watch()

侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。

1.1 语法

ts
// 侦听单个来源
function watch<T>(
  source: WatchSource<T>,
  callback: WatchCallback<T>,
  options?: WatchOptions
): WatchHandle

// 侦听多个来源
function watch<T>(
  sources: WatchSource<T>[],
  callback: WatchCallback<T[]>,
  options?: WatchOptions
): WatchHandle

type WatchCallback<T> = (
  newValue: T,
  oldValue: T,
  onCleanup: (cleanupFn: () => void) => void
) => void

type WatchSource<T> =
  | Ref<T> // ref
  | (() => T) // getter
  | T extends object
  ? T
  : never // 响应式对象

interface WatchOptions extends WatchEffectOptions {
  immediate?: boolean // 默认:false
  deep?: boolean | number // 默认:false
  flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
  once?: boolean // 默认:false (3.4+)
}

interface WatchHandle {
  (): void // 可调用,与 `stop` 相同
  pause: () => void
  resume: () => void
  stop: () => void
}

type DebuggerEvent = {
  effect: ReactiveEffect
  target: object
  type:
    | TrackOpTypes /* 'get' | 'has' | 'iterate' */
    | TriggerOpTypes /* 'set' | 'add' | 'delete' | 'clear' */
  key: any
  newValue?: any
  oldValue?: any
  oldTarget?: Map<any, any> | Set<any>
}

watch() 默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。

参数:

  • source:侦听器的。这个来源可以是以下几种:

    • 一个函数,返回一个值

    • 一个 ref

    • 一个响应式对象

    • 或是由以上类型的值组成的数组(监听多个数据)

  • callback

    • 在发生变化时要调用的回调函数。当监听单个数据源时,这个回调函数接受三个参数:
      • newValue:监听的数据改变后的新值
      • oldValue:监听的数据改变前的旧值
      • onCleanup:一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。
    • 当监听多个数据源时,这个回调接受两个数组
      • 第一个数组:依次为多个数据源改变后的新值
      • 第二个数组:依次为多个数据源改变前的旧值
  • options:一个可选的对象,可包含以下参数

    • immediate:在侦听器创建时立即触发回调。第一次调用时旧值是 undefined
    • deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。在 3.5+ 中,此参数还可以是指示最大遍历深度的数字。
    • flush:默认为pre,侦听器将在组件渲染之前执行
      • post:将会使侦听器延迟到组件渲染之后再执行
      • sync:在某些特殊情况下 (例如要使缓存失效),可能有必要在响应式依赖发生改变时立即触发侦听器
    • onTrack:将在响应属性或引用作为依赖项被跟踪时被调用
    • onTrigger:将在侦听器回调被依赖项的变更触发时被调用。
    • once:(3.4+) 回调函数只会运行一次。侦听器将在回调函数首次运行后自动停止。

返回值:返回一个函数,用于停止对数据源的监听

1.2 具体使用

监听一个ref

js
const num = ref(123)
watch(num, (newVal,oldVal) => {
    // ...
})

监听一个有返回值的函数,当使用ref或reactive定义对象时使用如下用法,回调只在此函数的返回值变化时才会触发

js
const state = reactive({ num:123 })
watch(
    ()=>state.num,
    (newVal,oldVal) => {
		// ...
    }
)

监听多个来源

js
const num = ref(123)
const num2 = ref(456)
const num3 = reactive({ num: 789 })
watch(
    [num, num2, () => num3.num],
    (
        [newNumValue, newNum2Value, newNum3Value],
        [oldNumValue, oldNum2Value, oldNum3Value]) => {
            console.log(newNumValue, newNum2Value, newNum3Value)
            console.log(oldNumValue, oldNum2Value, oldNum3Value)
    }
)
num.value = 1
num2.value = 1
num3.num = 1

当使用 getter 函数作为源时,回调只在此函数的返回值变化时才会触发。如果你想让回调在深层级变更时也能触发,你需要使用 { deep: true } 强制侦听器进入深层级模式。在深层级模式时,如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象。

js
const state = reactive({ 
    count: 0,
    cate:{
        num1:11,
        num2:22
    }
})
watch(
  () => state,
  (newValue, oldValue) => {
    // newValue === oldValue
  },
  { deep: true }
)

当直接侦听一个响应式对象时,侦听器会自动启用深层模式:

js
const state = reactive({ count: 0 })
watch(state, () => {
  /* 深层级变更状态所触发的回调 */
})

watch()watchEffect()享有相同的刷新时机和调试选项:

js
watch(source, callback, {
  flush: 'post',
  onTrack(e) {
    debugger
  },
  onTrigger(e) {
    debugger
  }
})

停止侦听器:

js
const stop = watch(source, callback)

// 当已不再需要该侦听器时:
stop()

副作用清理:

js
watch(id, async (newId, oldId, onCleanup) => {
  const { response, cancel } = doAsyncWork(newId)
  // 当 `id` 变化时,`cancel` 将被调用,
  // 取消之前的未完成的请求
  onCleanup(cancel)
  data.value = await response
})

3.5+ 中的副作用清理:

js
import { onWatcherCleanup } from 'vue'

watch(id, async (newId) => {
  const { response, cancel } = doAsyncWork(newId)
  onWatcherCleanup(cancel)
  data.value = await response
})

2.watchEffect()

watchEffect不需要指定的数据源,会自动检测副作用函数中用了哪些数据,当这些数据变化时,副作用函数(也就是第一个参数)会自动触发

2.1 语法

ts
function watchEffect(
  effect: (onCleanup: OnCleanup) => void,
  options?: WatchEffectOptions
): WatchHandle

type OnCleanup = (cleanupFn: () => void) => void

interface WatchEffectOptions {
  flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}

interface WatchHandle {
  (): void // 可调用,与 `stop` 相同
  pause: () => void
  resume: () => void
  stop: () => void
}

type DebuggerEvent = {
  effect: ReactiveEffect
  target: object
  type:
    | TrackOpTypes /* 'get' | 'has' | 'iterate' */
    | TriggerOpTypes /* 'set' | 'add' | 'delete' | 'clear' */
  key: any
  newValue?: any
  oldValue?: any
  oldTarget?: Map<any, any> | Set<any>
}

参数:

  • effect:要运行的副作用函数。
    • onCleanup:用来注册清理回调。清理回调会在该副作用下一次执行前被调用,可以用来清理无效的副作用,例如等待中的异步请求 (参见下面的示例)。
  • options:一个可选的选项,可以用来调整副作用的刷新时机或调试副作用的依赖。
    • flush:默认为pre,侦听器将在组件渲染之前执行
      • post:将会使侦听器延迟到组件渲染之后再执行
      • sync:在某些特殊情况下 (例如要使缓存失效),可能有必要在响应式依赖发生改变时立即触发侦听器
    • onTrack:将在响应属性或引用作为依赖项被跟踪时被调用
    • onTrigger:将在侦听器回调被依赖项的变更触发时被调用。

返回值:返回值是一个用来停止该副作用的函数。

2.2 具体使用

基本使用

js
const count = ref(0)

watchEffect(() => console.log(count.value))
// -> 输出 0

count.value++
// -> 输出 1

清除副作用

js
watchEffect(async (onCleanup) => {
  const { response, cancel } = doAsyncWork(id.value)
  // `cancel` 会在 `id` 更改时调用
  // 以便取消之前
  // 未完成的请求
  onCleanup(cancel)
  data.value = await response
})

3.5+ 中的副作用清理:

js
import { onWatcherCleanup } from 'vue'

watch(id, async (newId) => {
  const { response, cancel } = doAsyncWork(newId)
  onWatcherCleanup(cancel)
  data.value = await response
})

停止侦听器

js
const stop = watchEffect(() => {})

// 当不再需要此侦听器时:
stop()

配置选项

js
watchEffect(() => {}, {
  flush: 'post',
  onTrack(e) {
    debugger
  },
  onTrigger(e) {
    debugger
  }
})

3.watchPostEffect()

watchEffect()使用 flush: 'post' 选项时的别名。

4.watchSyncEffect()

watchEffect()使用 flush: 'sync' 选项时的别名。

5.onWatcherCleanup()

版本需要在3.5+

注册一个清理函数,在当前侦听器即将重新运行时执行。只能在 watchEffect 作用函数或 watch 回调函数的同步执行期间调用 (即不能在异步函数的 await 语句之后调用)。

类型

ts
function onWatcherCleanup(
  cleanupFn: () => void,
  failSilently?: boolean
): void

示例

ts
import { watch, onWatcherCleanup } from 'vue'

watch(id, (newId) => {
  const { response, cancel } = doAsyncWork(newId)
  // 如果 `id` 变化,则调用 `cancel`,
  // 如果之前的请求未完成,则取消该请求
  onWatcherCleanup(cancel)
})

MIT Licensed