前言
小编最近在复习vue,复习到vue的监听器,总结了以下几点:
完整代码示例
javascript
watch(
user, // 监听源
(newVal) => { // 回调函数
console.log('用户信息变化:', newVal)
},
{ deep: true } // 配置选项
)
一、核心概念拆解
1. 三大核心要素
要素 | 说明 | 类比生活场景 |
---|---|---|
监听源 | 被监视的数据源(用户信息对象) | 监控摄像头对准的保险柜 |
回调函数 | 数据变化时触发的操作函数 | 保险柜被打开时触发的警报 |
配置选项 | 控制监听行为的参数设置 | 监控摄像头的灵敏度设置 |
2. 参数详解
javascript
watch(source, callback, options)
二、逐层深入解析
1. 监听源(Source)
常见类型:
-
响应式对象 (
reactive
创建) -
ref 对象(含对象类型的 ref)
-
getter 函数 (
() => obj.prop
)
js
//来自官方文档
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. 与普通函数的区别
javascript
// 普通函数
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. 错误写法示例
js
// 错误!无法追踪依赖
watch(x.value + y.value, (sum) => {})
2. 正确写法原理
通过函数包装:
js
// ✅ 正确!建立响应式依赖链
watch(
() => x.value + y.value, // 依赖收集器
(sum) => { /* 响应变化 */ }
)
五、类比其他场景
1. 计算属性中的 getter
js
const total = computed({
get: () => x.value + y.value, // 同款getter
set: (val) => { /*...*/ }
})
2. 对象属性的 getter
js
const obj = {
get sum() { // 原生JS的getter
return x + y
}
}
特殊特性:
- 当监听整个对象时,默认不触发嵌套属性的变化
- 需要配合
deep: true
才能深度监听
2. 回调函数(Callback)
参数解析:
javascript
(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 的对比
javascript
// Vue 2 选项式 API
watch: {
user: {
handler(newVal) { /*...*/ },
deep: true
}
}
// Vue 3 组合式 API(更灵活)
const user = reactive({/*...*/})
watch(user, (newVal) => {/*...*/}, { deep: true })
五、最佳实践指南
1. 性能优化建议
-
避免过度深度监听 :只对必要对象开启
deep
-
使用精确监听路径 :
javascript// 优于深度监听 watch(() => user.address.city, (newCity) => {...})
-
及时清理监听 :
javascriptconst stopWatch = watch(...) onUnmounted(stopWatch) // 组件卸载时停止监听
2. 常见错误示例
javascript
// 错误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) { /* 处理数据 */ }
})
六、完整示例场景
用户资料编辑监控
javascript
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