什么是watch的深度监听?
p>在Vue3开发中,不少同学碰到对象、数组这类复杂数据监听时,总会疑惑“watch怎么才能监听内部变化?深度监听到底咋用?”今天就围绕Vue3 watch的深度监听,把原理、用法、坑点一次性讲明白,帮你搞定复杂数据的响应式跟踪~
先搞懂基础:Vue3里watch默认是「浅层监听」,比如监听一个基本类型(像字符串、数字),值变了watch回调会触发;但如果监听的是对象、数组这类「引用类型」,默认只有整个对象被重新赋值(引用变化)时,watch才会触发。
那深度监听是干啥的?开启后,watch会「递归遍历」对象的所有嵌套属性,哪怕只是修改对象里某个子属性(比如user.name
从“小明”改成“小李”)、数组里某个元素(比如list[0].price
变了),watch回调也能检测到,简单说,深度监听让watch能钻进复杂数据的“肚子里”,跟踪每一层变化~
什么时候需要开深度监听?
不是所有场景都要开深度监听,得看需求:
场景1:监听对象的嵌套属性变化
比如做用户信息编辑页,用户数据是{ name: '阿花', info: { age: 25, city: '成都' } }
,如果产品要求「改年龄或城市时,实时更新页面预览」,这时候对象内部属性变了,就得开深度监听。
场景2:监听数组元素的内部变化
比如购物车列表是[{ id: 1, name: '卫衣', num: 2 }, { id: 2, name: '裤子', num: 1 }]
,如果用户点击“+”修改某件商品数量(num
属性变了),要触发购物车总价重新计算,这时候数组里对象的内部属性变化,也得靠深度监听。
但要注意:深度监听会遍历所有子属性,性能开销比浅层大,如果只是替换整个对象(比如user = 新的用户对象
),用默认的浅层监听就够,没必要开deep
浪费性能~
Vue3里怎么配置深度监听?
配置很简单,给watch加个选项{ deep: true }
就行,不过得结合响应式数据的类型(reactive
/ref
/普通对象)注意细节:
情况1:监听reactive包裹的对象
比如定义const user = reactive({ name: '阿柴', age: 3 });
,监听写法:
watch(user, (newVal, oldVal) => {
console.log('用户数据变了', newVal, oldVal);
}, { deep: true });
这时改user.name = '阿喵'
,watch回调会触发。
情况2:监听ref包裹的对象
如果是const userRef = ref({ name: '阿柴', age: 3 });
,得注意!ref
的value
才是实际对象,所以watch要这么写:
watch(() => userRef.value, (newVal) => { ... }, { deep: true });
或者直接监听整个ref
(但内部原理是监听value
的引用,所以改内部属性时,默认不触发,必须开deep
):
watch(userRef, (newVal) => { ... }, { deep: true }); // 这样也能生效,但要理解是监听value的内部
情况3:监听普通对象(非响应式)
如果直接watch一个普通对象,比如const obj = { a: { b: 1 } };
,就算开了{ deep: true }
,修改obj.a.b
也不会触发watch!因为普通对象不是响应式的,Vue没法跟踪变化,所以得先把普通对象变成响应式(用reactive
或ref
包裹),再监听~
深度监听下newVal
和oldVal
容易“踩坑”:因为对象是引用类型,深度监听时newVal
和oldVal
可能指向同一个对象(修改的是同一个引用),比如修改user.name
后,newVal
和oldVal
其实是同一个对象(因为没重新赋值,只是改属性),所以如果要对比前后值,得自己做深拷贝或者存旧值~
深度监听的性能坑点咋避?
深度监听递归遍历属性,数据层级深、体积大时,容易卡!这几个技巧能救命:
技巧1:能不深度就不深度,精准监听更高效
如果只关心对象某一个属性(比如user.phone
),别开deep
!直接监听这个属性:
watch(() => user.phone, (newVal) => { ... });
这样性能好太多,因为只跟踪这一个属性的变化。
技巧2:配合immediate和flush优化执行时机immediate
控制「是否在watch创建时立即执行一次回调」,比如表单初始化时要验证,就开immediate: true
。flush
控制回调执行时机,可选pre
(默认,组件更新前执行)、post
(组件更新后执行)、sync
(同步执行),比如修改数据后要操作DOM,选post
更安全。
技巧3:大对象拆分,减少遍历压力
如果要监听的对象层级很深(比如树形结构数据),全量深度监听肯定卡,这时候可以拆分成多个小watch,或者用计算属性把需要监听的部分“摘出来”,比如树形数据只关心某一层节点,就单独监听那一层~
深度监听和浅层监听咋选?
核心逻辑是「看你要监听『引用变化』还是『内部属性变化』」:
浅层监听(默认):适合这两种情况
- 监听基本类型(string
/number
等),值变就触发;
- 监听对象/数组,但只关心「整个对象被替换」(比如user = 新对象
),不关心内部属性修改。
深度监听:适合必须跟踪「内部属性/元素变化」的场景
比如表单多字段实时验证、购物车商品数量修改这类,需要感知嵌套数据的每一次改动。
得结合Vue3的shallowReactive
/shallowRef
理解!shallowReactive
是「浅层响应式」:只有对象最外层属性是响应式,内部属性修改不触发响应式,这时候就算给watch开{ deep: true }
,也监听不到内部变化(因为shallowReactive
内部本来就没做响应式),所以用shallow
系列时,得更谨慎判断是否开深度监听~
实战案例:用深度监听做表单实时验证
举个实际开发场景:做注册表单,实时验证用户名、密码、邮箱是否符合规则。
步骤1:定义响应式表单数据
const form = reactive({
username: '',
password: '',
email: ''
});
步骤2:配置深度监听,实时验证
watch(form, (newForm) => {
// 验证用户名长度
if (newForm.username.length < 3) {
alert('用户名至少3位~');
}
// 验证密码复杂度
if (!/[A-Z]/.test(newForm.password)) {
alert('密码需包含大写字母~');
}
// 验证邮箱格式
if (!/^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$/.test(newForm.email)) {
alert('邮箱格式不对~');
}
}, { deep: true, immediate: true }); // immediate让页面加载时就验证初始值
步骤3:理解为什么用深度监听?
因为用户输入时,修改的是form.username
、form.password
这些「嵌套属性」,浅层监听抓不到,必须开deep
才能跟踪到每个字段的变化。
但实际项目里如果表单字段特别多,全量深度监听会有点慢,这时候可以优化:比如把每个字段单独监听,或者用计算属性组合验证逻辑,但这个案例主要演示深度监听的用法,所以用全对象监听更直观~
Vue3 watch的深度监听是处理复杂响应式数据的利器,但得清楚它的适用场景和性能代价,按需开启,能拆则拆」——优先用更精准的监听方式(比如监听具体属性),真需要跟踪嵌套结构时再开deep
,同时结合响应式数据的创建方式(reactive
/ref
)确保监听生效,把这些细节吃透,复杂数据的状态跟踪就稳了~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。