1. 侦听器概述
侦听器(Watchers)是 Vue 中用于响应数据变化并执行副作用的重要机制。与计算属性不同,侦听器专注于在状态变化时执行操作(如 DOM 更改、异步操作等)。
1.1 基本语法
TypeScript
import { watch } from 'vue'
watch(source, callback, options)
2. 侦听数据源类型
数据源类型 | 示例 | 说明 |
---|---|---|
单个 ref | watch(x, callback) |
直接侦听一个 ref |
Getter 函数 | watch(() => x.value + y.value, callback) |
通过函数返回要侦听的值 |
多个数据源 | watch([x, () => y.value], callback) |
同时侦听多个数据源 |
响应式对象 | watch(obj, callback) |
隐式创建深层侦听器 |
2.1 注意事项
不能直接侦听响应式对象的属性值:
TypeScript
// 错误方式
watch(obj.count, (count) => {
console.log(`Count is: ${count}`)
})
// 正确方式:使用getter函数
watch(
() => obj.count,
(count) => {
console.log(`Count is: ${count}`)
}
)
3. 深层侦听器
3.1 自动深层侦听
当直接侦听响应式对象时,会自动创建深层侦听器:
TypeScript
const obj = reactive({ count: 0, nested: { value: 1 } })
watch(obj, (newValue, oldValue) => {
// 任何嵌套属性变化都会触发
// 注意:newValue和oldValue是同一个对象
})
obj.nested.value++ // 会触发侦听器
3.2 选择性深层侦听
对于getter函数返回的对象,默认只侦听引用变化:
TypeScript
watch(
() => state.someObject,
(newValue, oldValue) => {
// 仅当state.someObject被替换时触发
}
)
可以使用deep选项强制深层侦听:
TypeScript
watch(
() => state.someObject,
(newValue, oldValue) => {
// 嵌套属性变化也会触发
},
{ deep: true } // 或 { deep: 3 } (Vue 3.5+,表示最大遍历深度)
)
TypeScript
watch(
() => state.someObject,
(newValue, oldValue) => {
// 嵌套属性变化也会触发
},
{ deep: true } // 或 { deep: 3 } (Vue 3.5+,表示最大遍历深度)
)
4. watchEffect
watchEffect自动追踪其同步执行期间访问的所有响应式属性:
TypeScript
// 使用watch
watch(
todoId,
async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
},
{ immediate: true }
)
// 使用watchEffect简化
watchEffect(async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
})
4.1 watch vs watchEffect 对比
特性 | watch | watchEffect |
---|---|---|
依赖追踪 | 只追踪明确侦听的数据源 | 自动追踪所有访问到的响应式属性 |
初始执行 | 需要immediate: true |
立即执行 |
参数 | (source, callback, options) |
(effect, options) |
适用场景 | 需要精确控制触发时机 | 依赖多个属性,简化代码 |
5. 高级选项
5.1 即时回调
TypeScript
watch(
source,
(newValue, oldValue) => {
// 立即执行,且当source改变时再次执行
},
{ immediate: true }
)
5.2 一次性侦听器 (Vue 3.4+)
TypeScript
watch(
source,
(newValue, oldValue) => {
// 当source变化时,仅触发一次
},
{ once: true }
)
5.3 回调触发时机
TypeScript
// 默认:父组件更新后,当前组件DOM更新前
watch(source, callback)
// DOM更新后执行
watch(source, callback, { flush: 'post' })
// 或使用别名
watchPostEffect(() => {})
// 同步执行
watch(source, callback, { flush: 'sync' })
// 或使用别名
watchSyncEffect(() => {})
6. 副作用清理
在处理异步操作时,可能需要清理过期的副作用:
6.1 Vue 3.5+ (推荐)
TypeScript
watch(id, (newId) => {
const controller = new AbortController()
fetch(`/api/${newId}`, { signal: controller.signal })
.then(/* ... */)
onWatcherCleanup(() => {
controller.abort() // 终止过期请求
})
})
6.2 兼容版本 (Vue 3.5之前)
TypeScript
// 在watch中
watch(id, (newId, oldId, onCleanup) => {
const controller = new AbortController()
fetch(`/api/${newId}`, { signal: controller.signal })
.then(/* ... */)
onCleanup(() => {
controller.abort()
})
})
// 在watchEffect中
watchEffect((onCleanup) => {
const controller = new AbortController()
fetch(`/api/${id.value}`, { signal: controller.signal })
.then(/* ... */)
onCleanup(() => {
controller.abort()
})
})
7. 停止侦听器
7.1 自动停止
在组件 setup 中同步创建的侦听器会自动绑定到组件实例,并在组件卸载时自动停止。
7.2 手动停止
异步创建的侦听器需要手动停止:
TypeScript
const unwatch = watchEffect(() => {
// 副作用
})
// 当不再需要时
unwatch()
8. 最佳实践与总结
8.1 选择指南
场景 | 推荐使用 |
---|---|
侦听特定数据源的变化 | watch |
依赖多个数据源 | watchEffect |
需要立即执行回调 | watchEffect 或 watch + immediate: true |
只需要响应一次变化 | watch + once: true |
需要访问更新后的DOM | watch /watchEffect + flush: 'post' |
8.2 性能考虑
-
避免不必要的深层侦听,特别是大型数据结构
-
使用once: true选项处理只需响应一次的情况
-
及时清理不再需要的侦听器,防止内存泄漏
-
对于复杂逻辑,考虑使用watch明确指定依赖源
8.3 代码组织建议
TypeScript
// 推荐:将相关侦听逻辑组织在一起
const state = reactive({
userId: 1,
userData: null,
loading: false
})
watch(
() => state.userId,
async (newId) => {
state.loading = true
try {
const response = await fetch(`/api/users/${newId}`)
state.userData = await response.json()
} catch (error) {
console.error('Failed to fetch user:', error)
} finally {
state.loading = false
}
},
{ immediate: true } // 立即获取初始数据
)
9. 总结图表

侦听器是Vue响应式系统中处理副作用的核心工具,正确使用它们可以构建出高效、可维护的应用程序。根据具体场景选择合适的侦听方式,并注意性能优化和资源清理,将大大提升代码质量。