Skip to content

侦听器

watch

基本使用

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

// 单个 ref
const question = ref('')
watch(question, async (newVal, oldVal) => {
  // ...
})

// getter 函数
const x = ref(0)
const y = ref(0)
watch(
  () => x.value + y.value,
  (val) => {
    // ...
  },
)

// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x is ${newX} and y is ${newY}`)
})

// 不能直接侦听响应式对象的属性值,需要用一个返回该属性的 getter 函数
const obj = reactive({ count: 0 })
watch(
  () => obj.count,
  (count) => {
    console.log(`Count is: ${count}`)
  },
)

深层侦听器

显式地加上 deep 选项,强制转成深层侦听器:

js
watch(
  () => state.someObject,
  (newVal, oldVal) => {
    // 注意:`newVal` 此处和 `oldVal` 是相等的
    // *除非* state.someObject 被整个替换了
  },
  { deep: true },
)

Vue 3.5+ 中,deep 选项还可以是一个数字,表示最大遍历深度 —— 即 Vue 应该遍历对象嵌套属性的级数。

即时回调的侦听器

js
watch(
  source,
  (newVal, oldVal) => {
    // 立即执行,且当 `source` 改变时再次执行
  },
  { immediate: true },
)

watchEffect

js
watchEffect(async () => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId.value}`)
  data.value = await response.json()
})

在上述代码中,回调会立即执行,不需要指定 immediate: true。在执行期间,它会自动追踪 todoId.value 作为依赖(和计算属性类似)。每当 todoId.value 变化时,回调会再次执行。

清理副作用

js
// 并且必须在 `watchEffect` 效果函数或 `watch` 回调函数的同步执行期间调用:不能在异步函数的 `await` 语句之后调用它。

import { watch, onWatcherCleanup } from 'vue'

watch(id, (newId) => {
  const controller = new AbortController()

  fetch(`/api/${newId}`, { signal: controller.signal }).then(() => {
    // 回调逻辑
  })

  onWatcherCleanup(() => {
    // 终止过期请求
    controller.abort()
  })
})
js
// 通过函数参数传递的 onCleanup 与侦听器实例相绑定,因此不受 onWatcherCleanup 的同步限制。

watch(id, (newId, oldId, onCleanup) => {
  onCleanup(() => {
    // 清理逻辑
  })
})

watchEffect((onCleanup) => {
  onCleanup(() => {
    // 清理逻辑
  })
})

回调的触发时机

post 侦听器

如果想在侦听器回调中能访问被 Vue 更新之后的所属组件的 DOM:

js
watch(source, callback, {
  flush: 'post',
})

watchEffect(callback, {
  flush: 'post',
})

watchPostEffect(() => {
  /* 在 Vue 更新后执行 */
})

async 侦听器

在 Vue 进行任何更新之前触发:

js
watch(source, callback, {
  flush: 'sync',
})

watchEffect(callback, {
  flush: 'sync',
})

watchSyncEffect(() => {
  /* 在响应式数据变化时同步执行 */
})

停止侦听器

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

// ...当该侦听器不再需要时
unwatch()

常见问题

watchwatchEffect 的区别?

watchwatchEffect 都能响应式地执行有副作用的回调。它们之间的主要区别是追踪响应式依赖的方式:

  • watch 只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch 会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。
  • watchEffect,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。

基于 MIT 许可发布