普通视图

发现新文章,点击刷新页面。
昨天 — 2026年1月25日首页

Vue 3中watch如何高效监听多数据源、计算结果与数组变化?

作者 kknone
2026年1月25日 15:48

多数据源监听

在Vue 3中,watch 允许我们同时监听多个响应式数据源,当其中任意一个数据源发生变化时,都会触发回调函数。这在需要同步处理多个数据变化的场景中非常实用,比如表单多字段联动验证、多条件组合筛选等。

基本用法

我们可以将多个数据源(ref、reactive对象或getter函数)放入一个数组中,作为watch的第一个参数。回调函数的第一个参数是所有数据源的新值组成的数组,第二个参数是旧值组成的数组。

import { ref, watch } from 'vue'

// 定义多个响应式数据
const username = ref('')
const password = ref('')
const rememberMe = ref(false)

// 同时监听三个数据源
watch(
  [username, password, rememberMe],
  ([newUsername, newPassword, newRememberMe], [oldUsername, oldPassword, oldRememberMe]) => {
    console.log(`用户名从 ${oldUsername} 变为 ${newUsername}`)
    console.log(`密码从 ${oldPassword} 变为 ${newPassword}`)
    console.log(`记住我状态从 ${oldRememberMe} 变为 ${newRememberMe}`)
    
    // 实际场景中可以在这里进行表单验证
    if (newUsername && newPassword) {
      console.log('表单字段已填写完整')
    }
  }
)

执行流程

flowchart LR
A[定义多个响应式数据] --> B[将数据源放入数组作为watch的监听源]
B --> C[任意数据源发生变化]
C --> D[触发回调函数]
D --> E[解构新值和旧值数组,处理业务逻辑]

Getter函数监听

当我们需要监听的目标不是直接的响应式数据,而是基于响应式数据计算出的值时,可以使用getter函数作为watch的监听源。这种方式让我们能够灵活定义监听的计算逻辑。

基本用法

Getter函数需要返回我们想要监听的计算结果,当这个结果发生变化时,watch就会触发回调函数。

import { reactive, watch } from 'vue'

// 定义响应式状态对象
const cart = reactive({
  items: [
    { id: 1, name: 'Vue 3 实战教程', price: 59, quantity: 1 },
    { id: 2, name: 'Vuex 从入门到精通', price: 39, quantity: 2 }
  ]
})

// 监听购物车的总金额
watch(
  // Getter函数:计算总金额
  () => cart.items.reduce((total, item) => total + item.price * item.quantity, 0),
  (newTotal, oldTotal) => {
    console.log(`购物车总金额从 ${oldTotal} 元变为 ${newTotal} 元`)
    
    // 实际场景中可以在这里更新结算按钮状态或显示优惠信息
    if (newTotal >= 100) {
      console.log('满足满减条件,可享受10元优惠')
    }
  }
)

// 修改购物车商品数量,触发watch
cart.items[0].quantity = 2

执行流程

flowchart LR
A[定义响应式对象] --> B[创建getter函数,返回计算后的值]
B --> C[将getter函数作为watch的监听源]
C --> D[计算值发生变化]
D --> E[触发回调函数]
E --> F[处理新的计算结果]

数组监听

在Vue 3中监听数组需要注意一些细节,因为Vue的响应式系统对数组的处理和普通对象有所不同。默认情况下,watch会监听数组的引用变化和数组方法(如pushpopsplice等)的调用,但不会监听数组元素的直接索引修改。

往期文章归档
免费好用的热门在线工具

监听数组整体变化

当使用数组方法修改数组时,watch会自动触发:

import { ref, watch } from 'vue'

const todoList = ref(['学习Vue 3', '编写项目实战'])

// 监听数组整体变化
watch(todoList, (newList, oldList) => {
  console.log('待办事项列表发生变化:', newList)
})

// 使用数组方法修改数组,触发watch
todoList.value.push('优化代码性能')
todoList.value.pop()

监听数组内部元素变化

如果需要监听数组元素的直接修改(如arr[0] = '新值'),需要开启deep选项:

import { ref, watch } from 'vue'

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

// 开启deep选项,监听数组内部元素变化
watch(numbers, (newNumbers, oldNumbers) => {
  console.log('数组元素发生变化:', newNumbers)
}, { deep: true })

// 直接修改数组元素,触发watch
numbers.value[0] = 100

执行流程

flowchart LR
A[定义响应式数组] --> B[使用watch监听数组,可选开启deep]
B --> C[修改数组]
C --> D{修改方式?}
D -->|数组方法| E[触发watch回调]
D -->|索引修改| F{是否开启deep?}
F -->|是| E
F -->|否| G[不触发watch回调]

课后Quiz

问题1

如何在Vue 3中同时监听多个响应式数据的变化?请写出代码示例。

答案解析: 可以将多个数据源放入数组中作为watch的第一个参数,回调函数会接收新值数组和旧值数组:

import { ref, watch } from 'vue'

const name = ref('')
const age = ref(0)

watch(
  [name, age],
  ([newName, newAge], [oldName, oldAge]) => {
    console.log(`姓名从 ${oldName} 变为 ${newName}`)
    console.log(`年龄从 ${oldAge} 变为 ${newAge}`)
  }
)

问题2

当需要监听响应式对象中多个属性的计算结果时,应该使用什么方式?请写出代码示例。

答案解析: 使用getter函数作为watch的监听源,在getter函数中计算需要监听的结果:

import { reactive, watch } from 'vue'

const product = reactive({
  stock: 100,
  sales: 30
})

// 监听剩余库存
watch(
  () => product.stock - product.sales,
  (newStock, oldStock) => {
    console.log(`剩余库存从 ${oldStock} 变为 ${newStock}`)
  }
)

问题3

为什么直接修改数组的索引元素时,watch默认不会触发?如何解决这个问题?

答案解析: Vue的响应式系统默认不会监听数组的索引修改,因为这在性能上是低效的。解决方法有两种:

  1. 开启deep选项,深度监听数组内部元素变化
  2. 使用Vue提供的数组方法(如pushsplice等)来修改数组

常见报错解决方案

报错1:watch source must be a ref, reactive object, getter function, or array of these

  • 原因watch的监听源类型不正确,不是Vue支持的响应式数据源类型。
  • 解决方法:确保监听源是ref、reactive对象、getter函数或这些类型的数组。例如,如果你想监听普通变量,需要先将其转换为ref:
// 错误用法:监听普通变量
let count = 0
watch(count, () => { /* ... */ })

// 正确用法:转换为ref
const count = ref(0)
watch(count, () => { /* ... */ })

报错2:数组元素修改后watch不触发

  • 原因:直接修改数组索引元素,Vue默认不监听这种变化。
  • 解决方法:开启deep选项,或者使用数组方法修改数组:
// 方法1:开启deep选项
watch(numbers, () => { /* ... */ }, { deep: true })

// 方法2:使用数组方法
numbers.value.splice(0, 1, 100)

报错3:Cannot read property 'value' of undefined

  • 原因:在getter函数或回调函数中访问了未定义的响应式属性。
  • 解决方法:确保所有访问的属性都已正确定义,或者添加可选链操作符:
// 错误用法:访问未定义的属性
watch(() => user.address.city, () => { /* ... */ })

// 正确用法:添加可选链
watch(() => user?.address?.city, () => { /* ... */ })

参考链接

参考链接:vuejs.org/guide/essen…

❌
❌