Vue3 Watch监听的深度实践与常见陷阱避坑指南
Watch监听的基本用法,别再写一堆if-else了
最近重构一个项目,发现之前的代码里全是这种东西:
// 老代码,每次数据变化都这么判断
function handleDataChange(newVal) {
if (someCondition) {
doSomething();
}
if (otherCondition) {
doOtherThing();
}
}
看着就头疼,现在直接用watch监听,清爽多了。Vue 3 Composition API里的watch用法,我踩过不少坑,今天分享下亲测有效的几种方式。
先来个最基础的监听单个响应式数据:
import { ref, watch } from 'vue'
export default {
setup() {
const count = ref(0)
// 监听count的变化
watch(count, (newVal, oldVal) => {
console.log('新值:', newVal, '旧值:', oldVal)
// 执行相关逻辑
})
return { count }
}
}
这个最简单,没啥说的。重点是监听对象属性的时候,很多人不知道怎么写。
深度监听和立即执行,这两个选项很重要
watch默认是非深度监听的,也就是说如果你监听一个对象,只修改对象内部属性,不会触发回调。这里我踩过好多次坑。
import { reactive, watch } from 'vue'
export default {
setup() {
const userInfo = reactive({
name: '张三',
profile: {
age: 25,
city: '北京'
}
})
// 普通监听,profile.age变化不会触发
watch(() => userInfo.name, (newVal) => {
console.log('用户名变了:', newVal)
})
// 深度监听,内部属性变化也能触发
watch(userInfo, (newVal) => {
console.log('用户信息变了:', newVal)
}, { deep: true })
// 立即执行,页面加载就执行一次
watch(() => userInfo.profile.city, (newVal) => {
console.log('城市变了:', newVal)
}, { immediate: true })
return { userInfo }
}
}
immediate这个选项特别有用,比如你有个搜索功能,想页面一加载就执行一次搜索,默认搜索全部数据,这时候就用immediate: true。deep也很重要,特别是监听表单对象的时候。
数组监听的坑,很多人都掉进去过
数组的监听也是个容易出错的地方。比如你有个购物车数组,想监听数组长度变化,直接这样写是不行的:
// 错误写法
const cartItems = ref([])
watch(cartItems, (newVal) => {
console.log('购物车数量变了')
}, { deep: true })
上面的写法,虽然能监听到数组整体的替换,但是数组的push、pop、splice等方法是不会触发监听的。正确的做法是监听数组的length属性或者用getter函数:
import { ref, watch } from 'vue'
export default {
setup() {
const cartItems = ref([])
// 监听数组长度
watch(() => cartItems.value.length, (newLen, oldLen) => {
console.log(商品数量从${oldLen}变为${newLen})
})
// 或者监听数组本身(需要deep)
watch(cartItems, (newVal) => {
console.log('购物车数据变了')
}, { deep: true })
// 添加商品的示例
const addItem = () => {
cartItems.value.push({ id: Date.now(), name: '新商品' })
}
return { cartItems, addItem }
}
}
需要注意的是,数组的mutation方法(push、pop、shift、unshift、splice、sort、reverse)会改变原数组,但某些情况下可能不会触发更新,这时候需要配合Vue的响应式规则使用。
多个数据源同时监听,这个场景用得最多
实际开发中经常遇到这种情况:需要监听多个变量,任何一个变化都要执行某个操作,比如表单验证。传统做法是给每个字段单独写监听器,其实可以一次性监听多个:
import { ref, watch } from 'vue'
export default {
setup() {
const username = ref('')
const password = ref('')
const confirmPassword = ref('')
// 同时监听多个ref
watch([username, password, confirmPassword], ([newUser, newPass, newConfirm]) => {
console.log('表单任一字段变化')
// 验证逻辑
validateForm(newUser, newPass, newConfirm)
})
function validateForm(user, pass, confirm) {
// 这里写具体的验证逻辑
if (pass && confirm && pass !== confirm) {
console.log('密码不一致')
}
}
return { username, password, confirmPassword }
}
}
这个写法比分别监听三个字段要简洁得多,而且回调函数的参数顺序和监听数组的顺序是一致的,很好理解。
异步操作中的watch,记得及时停止监听
这是个大坑!如果在异步请求中使用watch,一定要记得清理监听器,否则容易造成内存泄漏。我在一个项目中就遇到了这个问题,导致页面卡死。
import { ref, watch, onUnmounted } from 'vue'
export default {
setup() {
const searchQuery = ref('')
let stopWatching
// 开始监听
stopWatching = watch(searchQuery, async (newQuery) => {
if (!newQuery) return
// 防抖,避免频繁请求
clearTimeout(debounceTimer)
debounceTimer = setTimeout(async () => {
try {
const response = await fetch(https://jztheme.com/api/search?q=${newQuery})
const data = await response.json()
// 更新结果
} catch (error) {
console.error('搜索失败:', error)
}
}, 300)
})
// 记住要清理监听器
onUnmounted(() => {
if (stopWatching) {
stopWatching()
}
})
let debounceTimer
return { searchQuery }
}
}
watch函数返回一个停止监听的函数,调用这个函数就能停止监听。在组件卸载的时候记得调用它,避免不必要的资源消耗。
错误处理和异常情况
watch的回调函数中如果抛出异常,会影响后续的监听。所以建议在回调函数中做好错误处理:
watch(searchQuery, async (newQuery) => {
try {
// 可能出错的异步操作
const result = await someAsyncOperation(newQuery)
// 更新状态
} catch (error) {
console.error('监听回调出错了:', error)
// 记录错误状态,不影响其他逻辑
}
})
另外,watch不能监听原始类型(number、string、boolean)的普通变量,必须是ref或reactive创建的响应式数据才行。这一点新手很容易搞错。
性能优化的小技巧
当监听的数据量很大时,要注意性能问题。可以通过一些选项来优化:
- flush: ‘post’ – 在DOM更新后执行回调,适合需要获取更新后DOM的场景
- flush: ‘sync’ – 同步执行回调,一般不推荐,除非特殊需求
- flush: ‘pre’ – 默认值,在DOM更新前执行
// DOM更新后再执行
watch(someState, () => {
// 这里可以安全地访问更新后的DOM
}, { flush: 'post' })
// 减少不必要的触发
watch(() => someObject.someProperty, (newVal) => {
if (newVal === null || newVal === undefined) return
// 其他逻辑
}, { immediate: false })
还有一个常用的技巧是结合computed使用,只在计算结果变化时才触发监听:
const computedValue = computed(() => {
// 复杂的计算逻辑
return someExpensiveCalculation()
})
watch(computedValue, (newVal) => {
// 只有computedValue真正变化时才会执行
})
以上是我踩坑后的总结,watch监听的这些用法在实际项目中都很实用,特别是深度监听和立即执行这两个选项,用对了能省不少事儿。这个技巧的拓展用法还有很多,后续会继续分享这类博客。

暂无评论