Vue 3 watch如何利用immediate、once、deep选项实现初始化、一次性与深度监听?

36 阅读16分钟

一、watch 侦听器的基本概念

在 Vue 3 中,watch 是一个强大的响应式 API,用于监听特定数据源的变化,并在数据变化时执行自定义的副作用逻辑。默认情况下,watch惰性的——也就是说,只有当被监听的数据源发生变化时,回调函数才会被触发。这使得 watch 非常适合处理诸如数据请求、DOM 操作、状态同步等需要响应数据变化的场景。

基本使用示例(Composition API)

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

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

// 监听 question 的变化
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 会触发回调函数,调用外部 API 获取答案并更新页面。

二、immediate:立即执行的侦听器

什么是 immediate 选项

默认情况下,watch 不会在创建时立即执行回调函数,只有当数据源第一次发生变化时才会触发。但在某些场景下,我们希望在组件初始化时就执行一次回调函数,比如初始化数据请求默认状态设置等。这时就可以使用 immediate: true 选项。

代码示例

Options API 写法

export default {
  data() {
    return {
      userId: 1,
      userData: null
    }
  },
  watch: {
    userId: {
      handler(newUserId) {
        // 初始化时立即执行,之后 userId 变化时再次执行
        this.fetchUserData(newUserId)
      },
      immediate: true // 开启立即执行
    }
  },
  methods: {
    async fetchUserData(id) {
      const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
      this.userData = await res.json()
    }
  }
}

Composition API 写法

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

const userId = ref(1)
const userData = ref(null)

watch(
  userId,
  async (newUserId) => {
    const res = await fetch(`https://jsonplaceholder.typicode.com/users/${newUserId}`)
    userData.value = await res.json()
  },
  { immediate: true } // 开启立即执行
)
</script>

执行流程

组件初始化 → 创建 watch 侦听器 → 立即执行回调函数 → 后续数据源变化时再次执行回调

应用场景

  1. 初始化数据加载:页面加载时根据默认参数请求数据
  2. 默认状态校验:组件创建时立即检查初始状态是否符合要求
  3. 同步外部数据:初始化时同步 URL 参数或本地存储数据
往期文章归档
免费好用的热门在线工具

三、once:一次性侦听器

什么是 once 选项

once: true 选项(Vue 3.4+ 支持)可以让 watch 侦听器只在数据源第一次发生变化时执行回调函数,之后自动停止监听。这适用于只需要处理第一次数据变化的场景。

代码示例

Options API 写法

export default {
  data() {
    return {
      firstVisit: true,
      userAgreed: false
    }
  },
  watch: {
    userAgreed: {
      handler(newValue) {
        if (newValue) {
          console.log('用户首次同意条款,执行初始化操作')
          // 执行只需要一次的初始化逻辑
        }
      },
      once: true // 只执行一次
    }
  }
}

Composition API 写法

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

const userAgreed = ref(false)

watch(
  userAgreed,
  (newValue) => {
    if (newValue) {
      console.log('用户首次同意条款,执行初始化操作')
      // 执行只需要一次的初始化逻辑
    }
  },
  { once: true } // 只执行一次
)
</script>

执行流程

组件初始化 → 创建 watch 侦听器 → 数据源第一次变化 → 执行回调函数 → 自动停止监听

应用场景

  1. 用户首次操作处理:比如用户第一次同意隐私政策时执行初始化
  2. 一次性数据同步:只需要同步一次的外部数据
  3. 临时状态监听:只需要处理第一次状态变化的场景

四、其他常用选项

deep:深度监听

当监听的数据源是复杂对象时,默认情况下 watch 只会监听对象的引用变化,不会触发嵌套属性的变化。使用 deep: true 可以开启深度监听:

const user = reactive({
  name: 'Alice',
  address: {
    city: 'Beijing'
  }
})

watch(
  user,
  (newUser) => {
    console.log('用户信息变化了', newUser)
  },
  { deep: true } // 开启深度监听
)

// 修改嵌套属性会触发回调
user.address.city = 'Shanghai'

flush:回调执行时机

flush 选项用于控制回调函数的执行时机,可选值有:

  • 'pre'(默认):在组件更新前执行
  • 'post':在组件更新后执行(可以访问更新后的 DOM)
  • 'sync':同步执行(数据变化时立即执行,不建议在频繁变化的数据源上使用)
watch(
  userId,
  () => {
    // 可以访问更新后的 DOM
    console.log(document.getElementById('user-name').textContent)
  },
  { flush: 'post' }
)

课后 Quiz

问题 1

如何让 Vue 3 的 watch 侦听器在组件初始化时立即执行一次回调函数?

答案解析

使用 immediate: true 选项:

watch(
  source,
  (newValue) => {
    // 初始化时立即执行,之后数据源变化时再次执行
  },
  { immediate: true }
)

问题 2

在 Vue 3.4+ 中,如何实现一个只执行一次的 watch 侦听器?

答案解析

使用 once: true 选项:

watch(
  source,
  (newValue) => {
    // 只在数据源第一次变化时执行
  },
  { once: true }
)

问题 3

当监听复杂对象时,为什么修改嵌套属性不会触发 watch 回调?如何解决?

答案解析

因为默认情况下 watch 是浅监听,只监听对象的引用变化。解决方法是使用 deep: true 选项开启深度监听:

watch(
  complexObject,
  (newValue) => {
    // 嵌套属性变化时也会触发
  },
  { deep: true }
)

常见报错解决方案

报错 1:watch 监听对象属性时无反应

错误场景

const obj = reactive({ count: 0 })

// 错误写法:直接监听对象属性
watch(obj.count, (count) => {
  console.log('Count changed:', count)
})

报错原因watch 无法直接监听 reactive 对象的单个属性,需要使用 getter 函数。

解决方案

// 正确写法:使用 getter 函数
watch(
  () => obj.count,
  (count) => {
    console.log('Count changed:', count)
  }
)

报错 2:immediate 选项使用时 oldValueundefined

错误场景

watch(
  userId,
  (newValue, oldValue) => {
    console.log('Old user ID:', oldValue) // 初始化时 oldValue 为 undefined
  },
  { immediate: true }
)

报错原因:初始化时没有旧值,所以 oldValueundefined

解决方案:在回调函数中添加判断:

watch(
  userId,
  (newValue, oldValue) => {
    if (oldValue !== undefined) {
      console.log('User ID changed from', oldValue, 'to', newValue)
    } else {
      console.log('Initial user ID:', newValue)
    }
  },
  { immediate: true }
)

报错 3:once 选项无效

错误场景:使用 Vue 3.3 及以下版本时尝试使用 once: true 选项。

报错原因once 选项是 Vue 3.4+ 才新增的特性。

解决方案

  1. 升级 Vue 版本到 3.4+
  2. 手动实现一次性监听:
const unwatch = watch(
  source,
  (newValue) => {
    console.log('只执行一次')
    unwatch() // 执行后手动停止监听
  }
)

参考链接