Vue 的 watch 可通过函数式监听器稳定获取新旧值:回调函数直接接收 newVal 和 oldVal 参数,配合 immediate 和 deep 选项实现精准监听;推荐使用 getter 形式监听特定字段或 computed 提取目标值,避免全量 deep 影响性能。

Vue 的 watch 本身不直接提供“新值”和“旧值”的自动对比,但通过正确写法可以稳定拿到两者——关键在于使用 函数式监听器 + 第二个参数选项,而非对象式写法中的 handler 单独配置。
用函数签名直接接收新旧值(推荐)
最简洁可靠的方式是把 watch 回调写成函数,并启用 immediate 和 deep(如需)的同时,利用 Vue 的回调签名:
- 响应式数据为 ref 或 reactive 时,watch 回调默认接收两个参数:newVal 和 oldVal
- 首次执行(
immediate: true)时,oldVal为undefined,需手动判断 - 对对象/数组深度监听时,必须设
deep: true,否则只触发引用变化
示例:
const count = ref(0)watch(count, (newVal, oldVal) => {
if (oldVal !== undefined && newVal > oldVal) {
console.log('数值变大了', { old: oldVal, new: newVal })
}
}, { immediate: true, deep: false })
监听 reactive 对象的特定字段(避免全量 deep)
直接 watch 整个 reactive 对象并开启 deep: true 性能较差。更优解是用计算属性或解构方式精准监听目标字段:
- 用
computed提取关注的字段,再 watch 该计算属性(天然支持新旧值) - 或使用
watch(() => obj.field, (n, o) => {...})的 getter 形式,语义清晰且无需 deep - 注意:getter 方式无法捕获对象内部新增属性(除非用
proxy或ref包裹)
例如监听用户信息中的邮箱变更:
const userInfo = reactive({ name: 'Alice', email: 'a@b.com' })watch(() => userInfo.email, (newEmail, oldEmail) => {
if (oldEmail && newEmail !== oldEmail) {
validateEmail(newEmail)
}
})
处理数组变动:区分“内容变”和“引用变”
watch 数组时容易误判——push/pop 等方法不改变引用,deep: true 才能触发;而直接赋新数组会触发非 deep 监听。实际比对建议:
- 若关心“是否新增/删除元素”,用
deep: true+ 新旧数组 JSON.stringify 对比(仅限简单结构) - 若只关心长度变化,watch
() => list.length更轻量 - 复杂场景(如需知道哪项被删),搭配
onBeforeUpdate或用watchEffect+ 副作用缓存上一次状态
watchEffect 中手动存旧值(灵活但需管理)
当需要更细粒度控制或跨多个响应式源比对时,watchEffect 配合闭包变量更自由:
- 在 effect 外部定义一个 ref 存储上一次值,每次 effect 执行前读取、执行后更新
- 适合多依赖联合判断(如表单中 name 和 age 同时变化才校验)
- 注意:需手动处理初始化(第一次无旧值)、异步更新时机等问题
小技巧:用 onInvalidate 清理过期副作用,避免竞态问题。

















