Skip to content

侦听器

基本示例

watch 的作用是用于监测响应式属性的变化,当响应式属性发生改变时执行特定的操作

基础演示:

vue
<script setup>
import { ref, watch } from 'vue'

const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')
const loading = ref(false)

// 可以直接侦听一个 ref 的变动
watch(question, async (newQuestion, oldQuestion) => {	// 使用一个异步匿名箭头函数(也叫做回调函数)作为参数传递进去
  if (newQuestion.includes('?')) {
    loading.value = true
    answer.value = 'Thinking...'
    try {
      const res = await fetch('https://yesno.wtf/api')
      answer.value = (await res.json()).answer
    } catch (error) {
      answer.value = 'Error! Could not reach the API. ' + error
    } finally {
      loading.value = false
    }
  }
})
</script>

<template>
  <p>
    Ask a yes/no question:
    <input v-model="question" :disabled="loading" />
  </p>
  <p>{{ answer }}</p>
</template>

参数解释:

  • watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组。

  • 第二个参数是一个回调函数,函数的两个参数分别是该数据源改变前和改变后的值。

深层监听

如果你要监听的是一个对象或数组,并希望对其内部的嵌套属性进行监听,可以使用 deep: true 选项。这会监听对象的所有属性变化。deep 选项还可以是一个数字,表示最大遍历深度——即 Vue 应该遍历对象嵌套属性的级数。

vue
 <script>
	const state = reactive({
        user: {
            name: 'Alice',
            age: 25
        }
    });

    watch(obj, (newValue, oldValue) => {	// 监听整个对象
            // 在嵌套的属性变更时触发
            // 注意:`newValue` 此处和 `oldValue` 是相等的
            // 因为它们是同一个对象!
            console.log('User object changed:', newVal, oldVal);
        },
        { deep: true } // 启用深度监听
    );
</script>

即时回调

watch 默认是懒执行的:仅当数据源变化时,才会执行回调。

但可以通过传入 immediate: true 选项,在创建侦听器时,立即执行一遍回调。

vue
 <script>
	watch(
      source,
      (newValue, oldValue) => {
        // 立即执行,且当 `source` 改变时再次执行
      },
      { immediate: true }
    )
</script>

一次性监听器

使用 once: true 选项,监听器只会触发一次。

vue
<script>
    watch(
      source,
      (newValue, oldValue) => {
        // 当 `source` 变化时,仅触发一次
      },
      { once: true }
    )
</script>

自动追踪

一个新的函数,主要用于偷懒,省略了数据源参数以及immediate: true选项。

vue
<script>
    watchEffect(async () => {	// 会自动追踪内部用到的响应式数据,无需数据源参数
      const response = await fetch(
        `https://jsonplaceholder.typicode.com/todos/${todoId.value}`	
        // 会自动追踪 todoId.value,当该值被修改时,匿名函数(闭包)会重新执行
      )
      data.value = await response.json()
    })	// 在创建时会强制执行一次,无需 immediate: true 选项
</script>

等于

vue
<script>
    const todoId = ref(1)
    const data = ref(null)

    watch(
      todoId,	// 第一次使用 todoId
      async () => {
        const response = await fetch(
          `https://jsonplaceholder.typicode.com/todos/${todoId.value}`	// 第二次使用 todoId
        )
        data.value = await response.json()
      },
      { immediate: true }
    )
</script>

阻止覆盖过时的数据

监听器内部的匿名函数采用异步的方式执行,容易出现问题,若某次执行时间被延长导致数据无法被即时赋值,但更新的数据已经被赋值了,这时候要阻止之前执行的匿名函数,防止拿到过时的数据。

使用 onWatcherCleanup() API 来注册一个清理函数,当侦听器失效并准备重新运行时会被调用:

vue
<script>
    import { watch, onWatcherCleanup } from 'vue'

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

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

      onWatcherCleanup(() => {
        // 终止过期请求
        controller.abort()
      })
    })
</script>