watchEffect 和 watch 详解:相同点、区别、使用场景
在 Vue 3 组合式 API 中,watch 和 watchEffect 都是用来监听响应式数据变化并执行副作用的核心 API,它们底层依赖同一个响应式追踪系统,只是使用方式、追踪方式、适用场景不同。
一、相同点
- 核心作用一致:都是监听响应式数据(
ref/reactive/计算属性)变化,触发回调函数执行逻辑(如请求、DOM操作、数据修改)。 - 都支持停止监听:调用返回的停止函数,即可手动停止监听。
- 都支持清除副作用:回调中可以返回清理函数,在下次执行前/组件卸载时清理(如清除定时器、取消请求)。
- 都支持配置项:都可以配置
immediate(立即执行)、flush(执行时机)、deep(深度监听)等。 - 都在组件卸载时自动停止:无需手动处理内存泄漏。
二、核心区别(重点)
| 特性 | watch | watchEffect |
|---|---|---|
| 追踪方式 | 手动指定要监听的数据源 | 自动追踪回调中用到的所有响应式数据 |
| 惰性执行 | 默认惰性(数据变化才执行) | 默认非惰性(初始化立即执行一次) |
| 新旧值 | 可以同时获取 新值、旧值 | 只能获取新值,无法获取旧值 |
| 监听目标 | 可监听:单个值、多个值、对象属性、getter函数 | 只能监听回调中直接使用的响应式数据 |
| 精准度 | 高,只监听指定数据 | 低,回调中所有依赖都会触发 |
| 使用风格 | 命令式(明确写要监听谁) | 声明式(只写逻辑,自动依赖追踪) |
三、详细用法对比
1. watch:手动指定数据源,精准监听
特点:必须明确告诉它「要监听谁」,适合需要精准控制、需要新旧值的场景。
基础用法
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
const user = reactive({ name: '张三' })
// 1. 监听单个 ref
watch(count, (newVal, oldVal) => {
console.log('count变化', newVal, oldVal)
})
// 2. 监听多个数据源(数组形式)
watch(
[count, () => user.name],
([newCount, newName], [oldCount, oldName]) =>{
console.log('count或name变化')
}
)
// 3. 监听对象属性(必须用 getter 函数,否则监听不到)
watch(
() => user.name,
(newVal) => {
console.log('名字变化')
}
)
// 4. 立即执行 + 深度监听
watch(user, () => {}, { immediate: true, deep: true })
</script>
2. watchEffect:自动追踪依赖,简洁监听
特点:不用手动指定数据源,执行时会自动收集回调中用到的所有响应式依赖,依赖变化就执行;初始化立即执行一次。
基础用法
<script setup>
import { ref, reactive, watchEffect } from 'vue'
const count = ref(0)
const user = reactive({ name: '李四' })
// 自动监听 count 和 user.name
watchEffect(() => {
// 只要这里用到的响应式数据变了,就会重新执行
console.log('count:', count.value)
console.log('name:', user.name)
})
// 模拟修改依赖,触发执行
count.value++ user.name = '王五'
</script>
四、使用场景(最实用的总结)
✅ 什么时候用 watch?
- 需要获取 新值 + 旧值 时
- 只需要监听指定的某个/某几个数据(精准控制,避免不必要的执行)
- 监听的逻辑和数据源不是强绑定,需要灵活控制
- 监听复杂对象且需要深度监听、精准控制
- 不需要初始化立即执行(默认惰性)
典型场景:
- 路由参数变化,根据旧值/新值做不同逻辑
- 表单某个字段变化,单独处理
- 状态切换,需要对比前后值
✅ 什么时候用 watchEffect?
- 监听的数据源不固定/多个,不想手动一个个写
- 希望初始化立即执行(比如一进页面就加载数据)
- 代码追求简洁,依赖自动追踪
- 不需要旧值,只需要响应依赖变化
典型场景:
- 监听多个查询条件,自动触发搜索
- 一进页面就加载数据,依赖变化重新加载
- 自动更新 DOM / 文档标题
- 副作用逻辑依赖多个响应式数据
五、代码示例:最佳实践
1. watch 场景:需要新旧值
// 分页变化,需要对比页码,做滚动/请求优化
const page = ref(1)
watch(page, (newPage, oldPage) => {
if (newPage > oldPage) {
console.log('下一页,请求数据')
} else {
console.log('上一页,缓存数据')
} getList(newPage) })
2. watchEffect 场景:自动追踪多依赖
// 搜索:关键词、分类、排序任意变化,自动搜索
const keyword = ref('')
const category = ref('')
const sort = ref('')
watchEffect(() => {
// 自动监听三个值,任意一个变了就执行
searchList({
keyword: keyword.value,
category: category.value,
sort: sort.value
})
})
六、补充:停止监听 + 清除副作用 两者用法完全一致:
// 停止监听
const stop = watchEffect(...) stop()
// 手动停止 // 清除副作用(定时器/请求)
watchEffect((onCleanup) => {
const timer = setTimeout(() => {}, 1000)
// 下次执行前/卸载时自动清理
onCleanup(() => clearTimeout(timer))
})
总结
- 相同:都是监听响应式数据、处理副作用、支持停止/清理。
- 核心区别:
watch手动指定数据源、可拿新旧值;watchEffect自动追踪、简洁、无旧值。 - 使用口诀:
- 要精准、要新旧值 → 用
watch - 要简洁、自动追踪、立即执行 → 用
watchEffect
- 要精准、要新旧值 → 用