前言
小编最近在复习vue,复习到vue的监听器,总结了以下几点:
完整代码示例
watch(
user, // 监听源
(newVal) => { // 回调函数
console.log('用户信息变化:', newVal)
},
{ deep: true } // 配置选项
)
一、核心概念拆解
1. 三大核心要素
| 要素 | 说明 | 类比生活场景 |
|---|---|---|
| 监听源 | 被监视的数据源(用户信息对象) | 监控摄像头对准的保险柜 |
| 回调函数 | 数据变化时触发的操作函数 | 保险柜被打开时触发的警报 |
| 配置选项 | 控制监听行为的参数设置 | 监控摄像头的灵敏度设置 |
2. 参数详解
watch(source, callback, options)
二、逐层深入解析
1. 监听源(Source)
常见类型:
-
响应式对象(
reactive创建) -
ref 对象(含对象类型的 ref)
-
getter 函数(
() => obj.prop) -
//来自官方文档 const x = ref(0) const y = ref(0) // single ref watch(x, (newX) => { console.log(`x is ${newX}`) }) // getter watch( () => x.value + y.value, (sum) => { console.log(`sum of x + y is: ${sum}`) } ) // array of multiple sources watch([x, () => y.value], ([newX, newY]) => { console.log(`x is ${newX} and y is ${newY}`) })可能这里有人对getter函数不太理解,下面是我找的资料:
一、本质理解
getter(获取器)这个名字来源于它的核心功能:获取并返回一个值。就像去自动售货机买东西:- 普通函数:你按按钮 → 机器给你饮料(直接获取)
- getter函数:你按特定组合键 → 机器计算总价后给你结果(需要计算过程)
二、代码对照表
代码部分 现实类比 为什么叫 getter () => x.value + y.value收银员计算商品总价 需要"获取"计算结果 watch(..., (sum) => {})收银员发现总价变化时通知你 监控获取器返回值的变动
三、深入解析
1. 基本特征
- 输入:依赖其他值(x/y)
- 输出:计算后的新值(sum)
- 特性:每次访问都会重新计算(除非缓存)
2. 与普通函数的区别
// 普通函数 function getSum() { return x + y // 直接返回 } // getter函数(在watch中) () => x.value + y.value // 被监控的依赖关系,记住返回的是一个函数。若 const x = ref(1); const y = ref(2); const xy = () => x.value + y.value; console.log(xy);//() => x.value + y.value console.log(xy());// 3 需要调用才有值,或者使用computed属性3. Vue 的特别处理
当把这个函数传给
watch时:- 依赖追踪:Vue 自动记录函数内部访问的响应式变量(x.value/y.value)
- 重新计算:当任意依赖变化时,自动重新执行这个函数
- 触发回调:如果返回的新值 ≠ 旧值,就执行回调函数
四、为什么必须用这种写法?
1. 错误写法示例
// 错误!无法追踪依赖 watch(x.value + y.value, (sum) => {})2. 正确写法原理
通过函数包装:
// ✅ 正确!建立响应式依赖链 watch( () => x.value + y.value, // 依赖收集器 (sum) => { /* 响应变化 */ } )
五、类比其他场景
1. 计算属性中的 getter
const total = computed({ get: () => x.value + y.value, // 同款getter set: (val) => { /*...*/ } })2. 对象属性的 getter
const obj = { get sum() { // 原生JS的getter return x + y } }
特殊特性:
- 当监听整个对象时,默认不触发嵌套属性的变化
- 需要配合
deep: true才能深度监听
2. 回调函数(Callback)
参数解析:
(newValue, oldValue) => { /*...*/ }
- 第一个参数:变化后的新值
- 第二个参数:变化前的旧值
- 注意:对于对象类型,
oldValue会与newValue指向同一引用
3. 配置选项(Options)
| 选项 | 类型 | 默认值 | 作用说明 |
|---|---|---|---|
deep | boolean | false | 深度监听嵌套属性变化 |
immediate | boolean | false | 立即触发回调(初始值时) |
flush | string | 'pre' | 控制回调触发时机(pre/post/sync) |
三、深度监听原理
1. 工作机制图解
graph TD
A[user 对象] --> B[属性变更]
B --> C{deep: true?}
C -->|是| D[遍历所有子属性]
C -->|否| E[仅监听顶层属性]
D --> F[触发回调]
E --> G[不触发回调]
2. 典型使用场景
- 用户资料表单(嵌套多个字段)
- 购物车商品对象(多层数据结构)
- 复杂配置项对象
四、对比其他监听方式
1. watch vs watchEffect
| 特性 | watch | watchEffect |
|---|---|---|
| 依赖收集方式 | 显式指定监听源 | 自动收集回调中的依赖 |
| 初始执行 | 需配置 immediate: true | 立即执行 |
| 适用场景 | 精确控制监听目标 | 副作用操作(如日志记录) |
2. 与 Vue 2 的对比
// Vue 2 选项式 API
watch: {
user: {
handler(newVal) { /*...*/ },
deep: true
}
}
// Vue 3 组合式 API(更灵活)
const user = reactive({/*...*/})
watch(user, (newVal) => {/*...*/}, { deep: true })
五、最佳实践指南
1. 性能优化建议
- 避免过度深度监听:只对必要对象开启
deep - 使用精确监听路径:
// 优于深度监听 watch(() => user.address.city, (newCity) => {...}) - 及时清理监听:
const stopWatch = watch(...) onUnmounted(stopWatch) // 组件卸载时停止监听
2. 常见错误示例
// 错误1:直接修改监听源导致无限循环
watch(user, (newVal) => {
user.name = '新名字' // 会再次触发监听!
})
// 错误2:忘记处理异步操作
watch(user, async (newVal) => {
const data = await fetchData() // 需要处理可能的竞态条件
})
// 正确解决方案:使用 watchEffect + 清理函数
watchEffect(async (onCleanup) => {
let isValid = true
onCleanup(() => { isValid = false })
const data = await fetchData()
if (isValid) { /* 处理数据 */ }
})
六、完整示例场景
用户资料编辑监控
const user = reactive({
id: 1,
info: {
name: '张三',
address: {
city: '北京',
street: '朝阳区'
}
}
})
// 深度监听整个用户对象
watch(
user,
(newUser) => {
console.log('用户信息已修改,自动保存...')
autoSave(newUser)
},
{ deep: true, immediate: true }
)
// 精确监听城市变化
watch(
() => user.info.address.city,
(newCity) => {
updateMap(newCity)
}
)
关键总结:
- 使用
deep: true时要考虑性能影响- 优先使用精确监听路径替代深度监听
- 注意对象引用的特性,必要时使用深拷贝
- 异步操作要配合清理函数防止内存泄漏
官方文档参考:Vue 3 watch