目录
- 引言
- 什么是副作用(Effect)?
- watch的工作原理
- [watchEffect 的工作原理](#watchEffect 的工作原理)
- 依赖收集
- 最大的区别
- 执行时机区别
-
- [watch 默认不会立即执行](#watch 默认不会立即执行)
- [watchEffect 默认立即执行](#watchEffect 默认立即执行)
- 能拿到旧值吗?
- 可以监听多个来源吗?
- 性能区别
- cleanup(清理副作用)
- [什么时候用 watch?](#什么时候用 watch?)
- [什么时候用 watchEffect?](#什么时候用 watchEffect?)
- 二者对比总结
- 总结
引言
相比大家刚接触vue3的时候都有一个疑问:**既然都有监听作用,那为什么 Vue 要提供两个 API?什么时候该用 watch,什么时候该用 watchEffect?**实际上,他们设计的目的完全不同。
一句话总结:
- watch:我指定监听谁。
- watchEffect:Vue 自动帮我收集依赖。
下面从原理、使用方式、执行时机、性能、适用场景等方面详细讲解。
什么是副作用(Effect)?
解释:
vue官网经常提到一个词:Effect(副作用),什么叫副作用? 不是修改响应式数据本身,而是因为数据变化,需要额外执行一些操作。
例如:
javascript
const count = ref(0)
// count变化后发送请求
watch(count, () => {
fetchData()
})
这里
javascript
count变化
↓
发送请求
发送请求就是副作用。
常见的副作用包括:
- 请求接口
- 修改DOM
- LocalStorage
- SessionStorage
- console.log
- 开启定时器
- 销毁定时器
- 第三方库初始化
watch的工作原理
watch是"明确监听"
watch基本用法
javascript
const count = ref(0)
watch(count, () => {
console.log('count变化')
})
流程:
javascript
count
│
│
▼
watch
│
▼
callback
Vue 不会去分析 callback,他只知道,我监听count。 至于callback里面写的是什么,他不关心。
watch可以监听多个数据
javascript
watch([count, name], () => {
console.log('任意一个变化')
})
流程:
javascript
count ─┐
│
name ───┤
▼
watch
watch可以监听getter
javascript
watch(
() => user.age,
() => {}
)
注意这里监听的是user.age,而不是user
所以如果:
javascript
user.name = 'Tom'
是不会触发的。
watchEffect 的工作原理
watchEffect 不需要指定监听对象。
例如:
javascript
const count = ref(0)
watchEffect(() => {
console.log(count.value)
})
这里没有写watch(count),vue怎么知道监听count?
答案就是:运行一次 callback
第一次执行:
javascript
watchEffect(() => {
console.log(count.value)
})
执行过程中,vue发现读取了 count.value,于是自动记录:
javascript
Effect
│
▼
count
以后:
javascript
count变化
↓
重新执行 Effect
所以:watchEffect 的本质就是:执行一次函数,自动收集里面访问过的所有响应式数据
依赖收集
举个例子:
javascript
const count = ref(0)
const name = ref('Tom')
watchEffect(() => {
console.log(count.value)
})
这里只读取了count.value
vue建立了:
javascript
count
│
▼
effect
所以count.value++就会触发,在这例子里面name.value = 'Jack'不会触发,因为根本没有读取。
如果:
javascript
watchEffect(() => {
console.log(count.value)
console.log(name.value)
})
依赖就会变成:
javascript
count ─┐
│
name ───┤
▼
effect
这样任意一个变化都会执行了。
最大的区别
watch需要自己指定:
javascript
watch(source, callback)
watchEffect不用指定,Vue 自动分析:
javascript
watchEffect(() => {
console.log(count.value)
})
watch = 指定依赖;watchEffect = 自动依赖
执行时机区别
watch 默认不会立即执行
例如:
javascript
const count = ref(0)
watch(count, () => {
console.log('执行')
})
页面初始化:不会执行,只有count.value++的时候才会执行。
如果需要第一次执行:
javascript
watch(
count,
() => {},
{
immediate: true
}
)
流程:
javascript
创建watch
↓
立即执行一次
↓
以后变化继续执行
watchEffect 默认立即执行
例如:
javascript
watchEffect(() => {
console.log(count.value)
})
页面刚创建,立即执行:count = 0,以后变化的时候继续执行,所以他是相当于:
javascript
watch(
count,
callback,
{
immediate: true
}
)
但并不完全等价。
能拿到旧值吗?
watch可以
javascript
watch(count, (newValue, oldValue) => {
console.log(newValue)
console.log(oldValue)
})
输出:
javascript
1 0
2 1
3 2
watchEffect不可以
javascript
watchEffect(() => {})
只有重新执行,没有oldValue;因为:它不是监听某个变量,它只是重新执行整个 Effect。
可以监听多个来源吗?
watch可以
javascript
watch(
[count, name],
([newCount, newName], [oldCount, oldName]) => {
}
)
watchEffect不用 ,因为他会自动收集。
例如:
javascript
watchEffect(() => {
console.log(count.value)
console.log(name.value)
})
vue就已经知道,监听两个。
性能区别
javascript
watchEffect(() => {
console.log(user.value.name)
console.log(list.value)
console.log(config.value)
console.log(permission.value)
console.log(menu.value)
})
它读取了五个响应式对象,所以任意一个变化都会重新执行整个函数。
而watch只监听:
javascript
watch(
() => user.value.name,
() => {}
)
例如如上,只监听user.name,其他的
javascript
list
menu
config
变化不会执行。因此:如果 Effect 中读取的数据很多,watchEffect 的重新执行次数可能更多。
cleanup(清理副作用)
watchEffect
javascript
watchEffect((onCleanup) => {
const timer = setInterval(() => {
console.log('运行')
}, 1000)
onCleanup(() => {
clearInterval(timer)
})
})
流程:
javascript
执行Effect
↓
创建timer
↓
数据变化
↓
执行cleanup
↓
重新创建timer
否则每次都会新增:
javascript
timer1
timer2
timer3
timer4
watch 同样支持:
javascript
watch(source, (newValue, oldValue, onCleanup) => {
})
javascript
watch(searchKeyword, async (keyword, _, onCleanup) => {
const controller = new AbortController()
onCleanup(() => {
controller.abort()
})
const res = await fetch('/api/search', {
signal: controller.signal
})
// ...
})
这样当关键词快速变化时,可以取消上一次未完成的请求,避免旧请求返回覆盖新结果。
什么时候用 watch?
- 适合:✅ 明确知道监听谁
特点:
- 精准
- 性能更好
- 能拿旧值
- 可控制触发时机
- 更适合业务逻辑
什么时候用 watchEffect?
- 适合:不知道有哪些依赖。
javascript
watchEffect(() => {
title.value =
`${user.value.name}-${department.value.name}`
})
以后新增了company.value.name,不用改监听,vue会自动收集。
javascript
watchEffect(() => {
document.title =
user.value.name
})
javascript
watchEffect(() => {
localStorage.setItem(
'theme',
theme.value
)
})
二者对比总结
| 对比项 | watch | watchEffect |
|---|---|---|
| 是否需要指定监听对象 | ✅ 是 | ❌ 否,自动收集 |
| 是否立即执行 | ❌ 默认否(可 immediate: true) |
✅ 默认立即执行 |
| 是否能拿到旧值 | ✅ 可以 | ❌ 不可以 |
| 是否适合精准监听 | ✅ 非常适合 | ⚠️ 不适合 |
| 是否适合依赖较多的场景 | ⚠️ 需要手动维护 | ✅ 非常适合 |
| 是否支持清理副作用 | ✅ 支持 | ✅ 支持 |
| 是否更容易误监听 | ❌ 不容易 | ⚠️ 容易(函数中读取的所有响应式数据都会成为依赖) |
| 典型用途 | 请求接口、监听路由、监听 props | 同步 DOM、同步浏览器状态、自动推导副作用 |
javascript
watch
┌─────────────────────┐
│ 我明确告诉 Vue: │
│ "请监听这些数据" │
└─────────┬───────────┘
│
▼
数据变化 → 回调执行
watchEffect
┌─────────────────────┐
│ Vue,你自己去分析 │
│ 我用了哪些响应式数据 │
└─────────┬───────────┘
│
自动收集依赖
│
▼
任意依赖变化 → 整个 Effect 重新执行
总结
- watch:关注"谁变了",适合精确监听和业务逻辑
- watchEffect:关注"这个副作用依赖了什么",适合自动依赖收集和状态同步