很多刚从Vue2转向Vue3的开发者,或者初次接触组合式API的新手,常会在使用watch时遇到各种问题:“为什么监听对象没反应?”“新旧值怎么都是同一个?”“和computed有啥区别?”今天咱们就用最直白的方式,把Vue3里watch变量的使用逻辑、常见坑点和实战技巧一次性讲清楚。
Vue3的watch和Vue2有啥不一样?
如果你之前用的是Vue2的选项式API,那对watch的印象可能是写在watch
选项里,通过字符串路径监听数据,
// Vue2示例 watch: { 'user.name': function(newVal, oldVal) { // 监听user对象的name属性 } }
到了Vue3的组合式API中,watch的用法发生了两个关键变化:
第一,监听目标更灵活,Vue3的watch可以直接监听ref
、reactive
创建的响应式变量,或者一个返回响应式值的函数。
// 监听ref变量 const count = ref(0) watch(count, (newVal, oldVal) => { console.log(`count变了:${oldVal}→${newVal}`) }) // 监听reactive对象的某个属性(推荐用函数返回) const user = reactive({ name: '张三', age: 20 }) watch( () => user.name, (newName, oldName) => { console.log(`名字变了:${oldName}→${newName}`) } )
第二,响应式机制更明确,Vue3的响应式系统基于Proxy实现,默认情况下,直接监听reactive
对象时,只有对象被替换(比如整个对象被重新赋值)才会触发watch,而修改对象的属性(如user.name = '李四'
)不会触发——这和Vue2默认深度监听不同,需要手动开启deep: true
选项(后面会详细说)。
监听基本类型变量,最简单的情况怎么写?
如果要监听的是ref
创建的基本类型变量(如数字、字符串、布尔值),写法非常简单:直接把ref变量作为第一个参数传给watch,第二个参数是回调函数,接收新值和旧值。
举个例子:
import { ref, watch } from 'vue' const message = ref('初始消息') // 监听message的变化 watch(message, (newMsg, oldMsg) => { console.log(`消息更新了:${oldMsg} → ${newMsg}`) }) // 测试:修改message的值 message.value = '新消息' // 控制台输出:消息更新了:初始消息 → 新消息
这里有两个需要注意的点:
新旧值的获取:对于基本类型的ref变量,
oldVal
是修改前的值,newVal
是修改后的值,两者是独立的(因为基本类型是值传递)。立即执行回调:如果希望在组件初始化时就执行一次回调(比如加载数据),可以添加
immediate: true
选项:watch(message, (newMsg, oldMsg) => { console.log('初始化或变化时执行', newMsg) }, { immediate: true }) // 组件加载时会立即打印一次初始值
监听对象/数组,为什么有时候没反应?怎么解决?
这是新手最常踩的坑之一,比如用reactive
创建了一个对象,然后直接监听它:
const user = reactive({ name: '张三', info: { city: '北京' } }) // 错误写法:直接监听reactive对象 watch(user, (newUser, oldUser) => { console.log('user变了', newUser) }) // 修改user的属性(不会触发watch) user.name = '李四' // 修改user的嵌套属性(同样不会触发) user.info.city = '上海'
这时候控制台不会有任何输出,因为Vue3的watch默认不深度监听reactive
对象的属性变化,要解决这个问题,有两种方法:
方法1:监听对象的某个具体属性(推荐)
通过函数返回具体的属性路径,这样watch会自动追踪该属性的变化:
// 监听user.name的变化 watch( () => user.name, (newName, oldName) => { console.log('名字变了', oldName, '→', newName) } ) // 监听user.info.city的变化(嵌套属性) watch( () => user.info.city, (newCity, oldCity) => { console.log('城市变了', oldCity, '→', newCity) } )
这种方式的好处是:只监听需要的属性,性能更好,且能正确获取新旧值(尤其是嵌套属性)。
方法2:开启深度监听(deep选项)
如果确实需要监听整个对象或数组的所有变化(包括嵌套属性),可以添加deep: true
选项:
// 监听整个user对象的所有变化(包括嵌套属性) watch(user, (newUser, oldUser) => { console.log('user有任何变化', newUser) }, { deep: true }) // 修改user.name或user.info.city都会触发回调 user.name = '王五' user.info.city = '广州'
但要注意:deep: true
会增加性能开销,因为需要递归遍历对象的所有属性,所以尽量只在必要时使用,直接监听reactive
对象时,oldVal
和newVal
会是同一个对象(因为reactive是代理对象),如果需要对比变化,建议用方法1监听具体属性。
watch和computed,到底该用哪个?
这是另一个高频问题。
computed:用于“根据已有响应式数据计算新值”,有缓存机制(依赖不变时不会重新计算),适合需要返回一个计算结果的场景(比如总价=数量×单价)。
watch:用于“当数据变化时执行副作用操作”(比如发请求、修改DOM、写日志),适合需要响应数据变化并执行额外逻辑的场景。
举个具体例子:
假设要实现一个功能:当用户输入关键词时,自动搜索相关内容并显示结果,这时候:
用
computed
可以得到处理后的关键词(比如去除前后空格),但无法直接触发搜索请求。用
watch
监听关键词的变化,当关键词变化时调用搜索接口,才是正确的选择。
代码对比:
// computed示例:处理输入关键词(返回计算结果) const rawKeyword = ref('') const processedKeyword = computed(() => rawKeyword.value.trim()) // watch示例:监听关键词变化并触发搜索(执行副作用) watch(processedKeyword, (newKeyword) => { if (newKeyword) { fetchSearchResults(newKeyword) // 调用搜索接口 } })
使用watch时,这些坑千万别踩!
忘记清理副作用
如果watch的回调里启动了定时器、订阅了事件或创建了长连接,需要在组件卸载时清理,否则可能导致内存泄漏,Vue3的watch返回一个停止函数,可以手动调用,或者在组件卸载时自动执行。
正确写法:
const stop = watch(count, (newVal) => { const timer = setInterval(() => { console.log('定时任务执行', newVal) }, 1000) // 返回清理函数(重要!) return () => { clearInterval(timer) } }) // 手动停止监听(比如组件卸载前) // stop()
循环触发watch
如果在watch的回调里修改了被监听的值,可能会导致无限循环。const count = ref(0) watch(count, (newVal) => { if (newVal % 2 !== 0) { count.value += 1 // 修改被监听的值,可能触发下一次watch } })
解决方法是:在修改值之前检查是否需要修改,或者通过其他方式(如标志位)避免重复触发。
错误监听reactive对象的旧值
直接监听reactive
对象时,oldVal
和newVal
是同一个代理对象(因为reactive是响应式包装),所以无法通过对比新旧值来判断具体变化,这也是为什么前面推荐监听具体属性的原因。
watch的正确使用姿势
基本类型:直接监听ref变量,用
newVal
和oldVal
获取变化。对象/数组:优先监听具体属性(
() => obj.key
),需要深度监听时用deep: true
(注意性能)。立即执行:用
immediate: true
在初始化时触发回调。副作用清理:在回调中返回清理函数,避免内存泄漏。
和computed区分:computed用于计算值,watch用于响应变化执行操作。
最后提醒:Vue3的watch在组合式API中更灵活,但也需要更明确地管理依赖,多动手写几个例子,结合实际业务场景练习,很快就能掌握其中的规律,下次遇到“watch不生效”的问题,先检查是不是没开deep
,或者监听的是整个对象而不是具体属性——这两个是最常见的原因。
网友回答文明上网理性发言 已有0人参与
发表评论: