Vue 3 的 watch 默认不直接暴露新旧值,但可通过回调函数的第二个参数(适用于 ref/reactive 层属性)或手动缓存旧值(配合 deep 监听和 JSON/structuredClone)获取;watchEffect 的 onInvalidate 则适合副作用清理场景。

Vue 3 的 watch 默认不直接暴露新旧值,但通过合理写法完全可以拿到——关键在于「使用回调函数的第二个参数」和「手动缓存旧值」两种方式,适用于简单监听或深度比对等不同场景。
基础 watch:用回调参数直接获取新旧值(仅适用于 ref / reactive 的顶层属性)
当监听的是一个 ref 或 reactive 对象的**单个顶层响应式变量**时,watch 回调接收两个参数:新值(newValue)和旧值(oldValue),前提是开启 immediate: false(默认)且首次触发不执行(否则 oldValue 是 undefined)。
示例:
const count = ref(0)
watch(count, (newVal, oldVal) => {
console.log('count 变了:', oldVal, '→', newVal)
}, { immediate: false }) // 首次不触发,确保 oldVal 有值
⚠️ 注意:如果设置了 immediate: true,首次执行时 oldVal 为 undefined,需自行判断。
立即学习“前端免费学习笔记(深入)”;
监听 reactive 对象或深层属性:必须用 getter 函数 + 缓存旧值
监听 reactive 对象、数组或嵌套字段(如 user.profile.name)时,watch 回调的第二个参数不再自动提供有效旧值(尤其在对象引用未变但内部变化时)。此时推荐「手动缓存 + 深度比对」方案:
审查 Vue 管理后台的变更请求(Vue 2 + Element UI,Vue 3 + Element Plus)。重点关注最小化变更实现、上线风险检查以及回归检查清单生成。
- 用
ref显式保存上一次的快照(JSON 序列化或结构化克隆) - 在 watch 回调中对比当前值与缓存值,再更新缓存
- 对复杂结构,可用
lodash.isEqual或JSON.stringify(注意循环引用限制)辅助判断
示例(监听用户对象并识别具体字段变更):
const user = reactive({ name: '张三', age: 25, tags: ['vue'] })
const prevUser = ref(JSON.stringify(user))
watch(() => ({ ...user }), (cur) => {
const curStr = JSON.stringify(cur)
if (curStr !== prevUser.value) {
console.log('user 发生变化,差异:', diff(prevUser.value, curStr))
prevUser.value = curStr
}
}, { deep: true })
watchEffect + onInvalidate:适合副作用清理与“上一次状态”推导
若逻辑重点不在“比对”,而在「基于上次执行时的状态做清理或过渡处理」,watchEffect 的 onInvalidate 更自然:
- 每次新副作用运行前,自动触发上一次注册的
onInvalidate回调 - 可在其中保存上一状态、取消请求、清除定时器等
- 不是严格意义上的“新/旧值比对”,但解决了多数异步场景下的竞态问题
示例(防抖搜索请求):
watchEffect((onInvalidate) => {
const timer = setTimeout(() => {
api.search(query.value).then(data => list.value = data)
}, 300)
onInvalidate(() => clearTimeout(timer)) // 上次 query 变化时清理旧定时器
})
进阶技巧:封装可复用的 watchWithDiff 工具函数
为避免重复写缓存逻辑,可封装一个增强版 watch:
function watchWithDiff(source, callback, options = {}) {
let oldValue = undefined
watch(source, (newValue) => {
callback(newValue, oldValue)
oldValue = structuredClone ? structuredClone(newValue) : JSON.parse(JSON.stringify(newValue))
}, { ...options, deep: true })
}
// 使用
watchWithDiff(() => user, (cur, prev) => {
if (cur.age !== prev?.age) console.log('年龄变了!')
})
注意:structuredClone 在较新浏览器支持,兼容性要求高时仍建议用 JSON 方式并规避函数、undefined、Date 等不可序列化类型。

















