watch 和 watchEffect 的区别是什么
在 Vue 3 中,watch 和 watchEffect 都是用于响应式数据的副作用(Side Effect)处理工具(即当数据变化时执行某些操作),但它们在依赖追踪方式、执行时机和获取数据上有着明显的区别。
以下是它们的核心区别总结:
1. 依赖追踪方式 (核心区别)
watch(显式指定依赖):- 你需要明确告诉它要监听哪个数据源(ref, reactive 对象, getter 函数或数组)。
- 它不会追踪回调函数内部访问的变量,只追踪你显式传入的第一个参数。
watchEffect(自动/隐式收集依赖):- 你不需要指定监听谁。
- 它会自动追踪回调函数内部用到的所有响应式数据。只要函数里用到了某个 ref,该 ref 变化时函数就会重新执行。
2. 执行时机 (初始化)
watch(懒执行 - Lazy):- 默认情况下,它不会在组件渲染时立即执行。只有当监听的数据源发生变化时,回调函数才会执行。
- 注:可以通过配置
{ immediate: true }让其立即执行。
watchEffect(立即执行 - Eager):- 它在初始化时立即执行一次。这是必须的,因为它需要运行一次代码来知道里面到底用到了哪些数据(从而收集依赖)。
3. 获取新旧值
watch:- 回调函数接收两个参数:
(newValue, oldValue)。 - 你可以很方便地拿到变化前后的值,适合做数据比对。
- 回调函数接收两个参数:
watchEffect:- 回调函数不接收
newValue和oldValue。 - 它只负责“当依赖变了,我就重跑一遍”,不关心具体是哪个值变了或者变成了什么。
- 回调函数不接收
代码对比示例
假设我们有两个响应式数据 count 和 name。
使用 watch
javascript
import { ref, watch } from 'vue'
const count = ref(0)
// 1. 必须显式传入要监听的源 (count)
// 2. 默认不执行,只有 count 变了才打印
// 3. 可以拿到新旧值
watch(count, (newVal, oldVal) => {
console.log(`Count 变了: 从 ${oldVal} 变为 ${newVal}`)
})
使用 watchEffect
javascript
import { ref, watchEffect } from 'vue'
const count = ref(0)
const name = ref('Vue')
// 1. 不需要传入源
// 2. 立即执行一次打印日志
// 3. 自动追踪:如果在里面用了 count 和 name,任意一个变化都会触发重新执行
watchEffect(() => {
console.log(`当前状态: ${name.value} - ${count.value}`)
})
详细对比表
| 特性 | watch |
watchEffect |
|---|---|---|
| 依赖来源 | 显式指定 (第一个参数) | 自动收集 (函数内用到的响应式数据) |
| 首次执行 | 懒执行 (数据变化时才跑) | 立即执行 (组件加载时先跑一次) |
| 访问旧值 | 支持 (newValue, oldValue) |
不支持 |
| 深度监听 | 监听 Ref 默认为浅层 (需 deep: true)监听 Reactive 对象默认为深层 |
自动追踪访问到的属性 (看起来像深层) |
| 适用场景 | 需要旧值、需要精确控制触发时机、只需监听特定数据 | 逻辑依赖多个数据、初始化就需要执行、不在乎旧值 |
什么时候用哪个?
使用
watchEffect的场景:- 当你有一个逻辑依赖于多个响应式数据,且你不想要把它们一个个列出来时。
- 例如:根据
userId和filter自动发起网络请求获取数据(请求通常需要立即发起一次,且参数变了要重新发起)。
使用
watch的场景:- 当你需要比较数据的前一个值和当前值时。
- 当你希望在数据变化时执行操作,但不希望在初始化时执行。
- 当你只想监听某个特定数据的变化,而忽略回调函数内部使用的其他响应式数据时(避免不必要的触发)。
总结
watch更像 Vue 2 的this.$watch,更加精确和可控。watchEffect更像 React 的useEffect(不带依赖数组版)或 Vue 的computed(但是没有返回值),更加智能和简洁。