Vue 的 watch 详解
watch 是 Vue 中用于观察和响应数据变化的重要特性。下面我将从多个方面全面详细地讲解 Vue 中的 watch。
1. watch 的基本概念
1.1 什么是 watch
watch 是 Vue 提供的一种更通用的方式来观察和响应 Vue 实例上的数据变动。当需要在数据变化时执行异步或开销较大的操作时,watch 是最有用的。
1.2 基本用法
// 选项式 API
export default {
data() {
return {
count: 0
}
},
watch: {
count(newVal, oldVal) {
console.log(`count 从 ${oldVal} 变为 ${newVal}`)
}
}
}
// 组合式 API
import { ref, watch } from 'vue'
const count = ref(0)
watch(count, (newVal, oldVal) => {
console.log(`count 从 ${oldVal} 变为 ${newVal}`)
})
2. watch 的多种使用方式
2.1 观察简单数据
watch: {
username(newVal, oldVal) {
// 当 username 变化时执行
}
}
2.2 观察嵌套属性
watch: {
'user.name'(newVal) {
// 观察嵌套属性
}
}
2.3 观察对象
watch: {
user: {
handler(newVal, oldVal) {
// 注意:对于对象和数组,newVal 和 oldVal 会是同一个引用
},
deep: true // 深度观察
}
}
2.4 立即触发
watch: {
value: {
handler(newVal) {
// 立即执行一次
},
immediate: true
}
}
3. watch 的工作原理
3.1 响应式系统集成
- Vue 的响应式系统会追踪依赖
- 当被观察的属性变化时,所有依赖它的 watcher 会被通知
- watcher 会执行回调函数
3.2 与 computed 的区别
| 特性 | watch | computed |
|---|---|---|
| 用途 | 观察数据变化执行副作用 | 基于依赖计算新值 |
| 缓存 | 无 | 有(依赖不变不重新计算) |
| 异步操作 | 支持 | 不支持 |
| 返回值 | 无 | 必须返回一个值 |
4. watch 的高级用法
4.1 观察多个源(Vue 3)
// 组合式 API
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})
4.2 停止观察
// 组合式 API
const stop = watch(source, callback)
// 当不再需要时
stop()
4.3 观察响应式对象的部分属性(Vue 3)
watch(
() => state.someObject.someProperty,
(newVal, oldVal) => {
// 仅观察特定属性
}
)
4.4 控制触发时机
watch: {
searchQuery: {
handler(newVal) {
// 执行操作
},
flush: 'post', // 在组件更新后触发
debounce: 500 // 防抖 500ms
}
}
5. watch 的性能考虑
5.1 避免过度使用 deep
- 深度观察会遍历对象的所有属性,性能开销大
- 尽量指定具体路径而非使用 deep
5.2 合理使用 immediate
- 只在确实需要初始值时使用
- 避免不必要的初始调用
5.3 大型数据结构的优化
- 对于大型对象或数组,考虑观察特定属性而非整个对象
- 或使用计算属性作为中间层
6. watch 的常见使用场景
6.1 表单验证
watch: {
username(newVal) {
this.validateUsername(newVal)
}
}
6.2 路由变化响应
watch: {
'$route'(to, from) {
// 响应路由变化
}
}
6.3 数据过滤
watch: {
searchText: {
handler() {
this.filteredItems = this.items.filter(item =>
item.text.includes(this.searchText)
},
immediate: true
}
}
6.4 异步操作
watch: {
id: {
handler(newVal) {
this.fetchData(newVal)
},
immediate: true
}
}
7. Vue 2 和 Vue 3 的 watch 区别
| 特性 | Vue 2 | Vue 3 |
|---|---|---|
| 多个源观察 | 不支持 | 支持数组形式 |
| 停止观察 | this.$unwatch | 返回停止函数 |
| 观察方式 | 字符串路径或函数 | 主要使用 getter 函数 |
| 性能优化 | 较少 | 更高效的响应式系统 |
8. watch 的源码解析(简化)
// 简化的 watcher 实现
class Watcher {
constructor(vm, expOrFn, cb, options) {
this.vm = vm
this.cb = cb
this.deps = []
// 解析表达式或获取函数
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
}
this.value = this.get()
}
get() {
pushTarget(this) // 设置当前 watcher
const value = this.getter.call(this.vm, this.vm)
popTarget() // 恢复之前的 watcher
return value
}
update() {
const oldValue = this.value
this.value = this.get()
this.cb.call(this.vm, this.value, oldValue)
}
}
9. watch 的最佳实践
- 明确观察目标:尽量观察具体的属性而非整个对象
- 合理使用 deep:只在必要时使用深度观察
- 考虑防抖:对于频繁变化的数据使用防抖
- 清理副作用:在组件销毁前清理异步操作
- 组合式思考:在 Vue 3 中考虑将相关逻辑组合在一起
10. 常见问题解答
Q1: watch 和 computed 该如何选择?
- 使用
computed当需要基于其他数据计算派生值 - 使用
watch当需要在数据变化时执行副作用(如异步操作、复杂逻辑)
Q2: 为什么 watch 对象时 newVal 和 oldVal 相同?
- 这是 JavaScript 的对象引用特性,对于对象和数组,Vue 只存储引用
- 如果需要比较旧值,可以深度克隆对象或使用特定属性观察
Q3: watch 能观察 Vuex 状态吗?
- 可以,使用计算属性作为中间层:
computed: { count() { return this.$store.state.count } }, watch: { count(newVal) { // ... } } - Vue 3 中可以直接使用
watch(() => store.state.count, callback)
总结
Vue 的 watch 是一个强大的特性,它:
- 提供了灵活的数据观察方式
- 支持多种观察模式(深度、立即执行等)
- 适合处理异步操作和副作用
- 在 Vue 3 中得到了进一步增强
合理使用 watch 可以使你的 Vue 应用更加响应式和健壮,特别是在处理复杂状态逻辑和异步操作时。