用Vue3开发的小伙伴都知道,组合式API(Composables)带来了更灵活的逻辑复用方式,而其中watch
函数作为响应式监听的核心工具,和选项式API里的watch
既有关联又有差异,很多新手在切换到组合式API时,经常遇到“监听不生效”“旧值拿不到”“深度监听没反应”等问题,今天就用问答形式,结合实际开发场景,把Vue3的watch
Composable讲透。
Q1:Watch Composable和选项式API的watch有什么本质区别?
选项式API的watch
是“声明式”的,写在watch
选项里,和组件实例强绑定;而组合式API的watch
是“函数式”的,直接在setup
或自定义Composable中调用,更灵活也更需要手动管理。
举个例子,选项式API里监听一个数据可能这样写:
// 选项式API export default { data() { return { count: 0 } }, watch: { count(newVal, oldVal) { console.log(`count变了:${oldVal}→${newVal}`) } } }
而组合式API中则需要主动调用watch
函数:
// 组合式API import { ref, watch } from 'vue' export default { setup() { const count = ref(0) watch(count, (newVal, oldVal) => { console.log(`count变了:${oldVal}→${newVal}`) }) return { count } } }
差异不止在写法,更在控制粒度,组合式的watch
可以监听更复杂的数据源(比如多个ref、计算属性、甚至自定义函数返回值),还能通过返回的停止函数手动取消监听,这在动态创建监听的场景(比如根据用户操作动态绑定)中特别有用。
Q2:基础用法怎么上手?常见写法有哪些?
watch
的核心参数是“监听源”和“回调函数”,可选配置项(如deep
、immediate
),新手最需要掌握的是如何正确定义“监听源”。
情况1:监听单个ref
这是最常见的场景,直接传入ref变量,回调会在ref的值变化时触发:
const searchKey = ref('') watch(searchKey, (newKey, oldKey) => { if (newKey) { fetchData(newKey) // 输入变化时触发搜索 } })
情况2:监听reactive对象的属性
如果用reactive
定义对象,直接监听对象本身可能不触发(除非整个对象被替换),但监听其属性需要用“函数返回属性”的形式:
const user = reactive({ name: '张三', age: 20 }) // 正确:监听name属性 watch( () => user.name, (newName, oldName) => { console.log(`姓名变更:${oldName}→${newName}`) } ) // 错误:直接监听user对象,除非整个对象被替换,否则不触发 watch(user, () => { ... })
情况3:监听多个源watch
支持传入数组,同时监听多个数据源,回调的参数会按顺序对应新值和旧值数组:
const keyword = ref('') const page = ref(1) watch( [keyword, page], ([newKeyword, newPage], [oldKeyword, oldPage]) => { if (newKeyword !== oldKeyword || newPage !== oldPage) { loadList(newKeyword, newPage) // 关键词或页码变化时重新加载列表 } } )
Q3:监听多个数据源时需要注意什么?
最容易踩的坑是“旧值的获取”,当监听多个源时,旧值数组中的每个值是上一次更新前的状态,但如果是初始加载(第一次触发),旧值可能为undefined
,比如上面的例子中,第一次进入页面时,oldKeyword
和oldPage
都是undefined
,这时候需要做非空判断:
watch( [keyword, page], ([newKeyword, newPage], [oldKeyword, oldPage]) => { // 避免初始加载时触发无效请求 if (oldKeyword !== undefined || oldPage !== undefined) { loadList(newKeyword, newPage) } } )
如果多个源同时变化(比如同一事件中修改了keyword
和page
),watch
会合并为一次回调执行,这比选项式API分别触发更高效,但需要注意业务逻辑是否需要区分“单独变化”和“同时变化”的场景。
Q4:深度监听和立即执行该怎么正确配置?
深度监听(deep)
当需要监听对象或数组内部变化时(比如user.info.address
修改),必须配置deep: true
,但要注意:reactive
对象本身已经是响应式的,直接监听其属性变化不需要deep
,只有监听嵌套对象或数组时才需要。
错误示范:
const goods = reactive({ list: [{ name: '苹果' }, { name: '香蕉' }] }) // 直接监听goods.list,修改某个元素的name不会触发 watch(goods.list, () => { ... })
正确写法:
// 方式1:监听整个对象并开启deep(不推荐,影响性能) watch(goods, () => { ... }, { deep: true }) // 方式2:精准监听嵌套属性(推荐) watch( () => goods.list.map(item => item.name), () => { ... } ) // 方式3:必须深度监听时明确配置(适用于无法精准监听的场景) watch( () => goods.list, () => { ... }, { deep: true } )
立即执行(immediate)
设置immediate: true
后,watch
会在初始化时立即执行一次回调,适合需要“先加载一次数据”的场景,比如进入页面时根据初始的searchKey
const searchKey = ref('手机') watch( searchKey, (key) => { loadGoodsList(key) // 初始化时立即执行 }, { immediate: true } )
注意:immediate
和deep
可以同时配置,但要避免在立即执行的回调中修改监听源,可能导致无限循环(比如回调里修改searchKey
,又触发监听)。
Q5:开发中常见的坑有哪些?如何避免?
坑1:监听reactive对象的属性时忘记用函数返回
新手可能直接写watch(user.name, ...)
,但user.name
是原始值(如字符串),不是响应式引用,正确做法是用() => user.name
作为监听源,这样watch
会跟踪这个函数的依赖,当user.name
变化时触发。
坑2:旧值(oldVal)不是预期值
在异步更新场景中,比如在回调里用setTimeout
修改数据,旧值可能不是上一次的“实时值”,这是因为Vue的响应式系统是异步批量更新的,旧值保存的是“更新前的状态”,如果需要严格的“变化前的值”,可以考虑手动保存状态:
let prevVal watch( () => user.age, (newVal) => { console.log(`之前是${prevVal},现在是${newVal}`) prevVal = newVal // 手动记录旧值 }, { immediate: true } )
坑3:忘记清理监听导致内存泄漏
在自定义Composable中动态创建的watch
,如果组件卸载时没有清理,可能导致回调继续执行,引发错误。watch
函数会返回一个停止函数,需要在onUnmounted
中调用:
import { onUnmounted } from 'vue' function useDynamicWatch(target) { const stop = watch(target, () => { ... }) onUnmounted(() => { stop() // 组件卸载时停止监听 }) }
坑4:过度使用深度监听影响性能deep: true
会递归遍历对象所有属性,对象层级越深、属性越多,性能消耗越大,尽量通过精准监听具体属性(如() => user.info.address
)替代监听整个对象,或者在必要时结合computed
优化:
const address = computed(() => user.info.address) watch(address, () => { ... }) // 只监听address的变化
灵活运用Watch Composable的关键
Vue3的watch
Composable本质上是“响应式依赖追踪”的工具,核心是理解“监听源”如何触发回调,掌握它的关键在于:
区分
ref
、reactive
的监听方式,避免直接监听原始值;合理使用
deep
和immediate
,避免性能浪费;手动管理监听的生命周期,防止内存泄漏;
结合
computed
优化复杂监听逻辑,提升代码可维护性。
从选项式API过渡到组合式API,watch
的用法变化看似不大,但灵活性和自由度提升了一个台阶,只要理解其设计逻辑,避开常见陷阱,就能轻松用它实现各种复杂的响应式需求,下次遇到“监听不生效”的问题,不妨回头检查一下监听源是否正确、是否需要配置deep
,或者是不是忘记清理旧监听了~
网友回答文明上网理性发言 已有0人参与
发表评论: