详细讲解Vue 3 组合式 API 中watch和watchEffect的核心区别,以及它们的实际使用技巧,以便在开发中根据场景正确选择合适的侦听器。
一、先明确两者的核心定位
在 Vue 3 组合式 API 中,watch和watchEffect都是用于监听响应式数据变化并执行副作用的工具,但设计理念和使用场景有明显区别:
watch:显式监听指定的响应式数据源,惰性执行(默认),支持访问新旧值,控制力更强。watchEffect:隐式监听函数内用到的所有响应式数据源,立即执行(默认),自动追踪依赖,写法更简洁。
二、核心区别对比(表格 + 示例)
| 对比维度 | watch | watchEffect |
|---|---|---|
| 依赖追踪方式 | 显式指定监听源(需明确告诉要监听什么) | 隐式追踪(自动收集函数内用到的响应式数据) |
| 执行时机 | 默认懒执行(仅当监听源变化时执行) | 默认立即执行(组件挂载时先执行一次,再监听变化) |
| 新旧值访问 | 支持(回调函数接收新值、旧值参数) | 不支持(只能访问当前最新值) |
| 监听源类型 | 可监听单个值、多个值、对象属性、计算属性等 | 只能监听函数内直接使用的响应式数据 |
| 停止监听 | 返回停止函数,或依赖组件卸载自动停止 | 同左(返回停止函数,组件卸载自动停止) |
| 清除副作用 | 需通过onInvalidate回调(Vue 3.2 + 推荐) | 同左(回调函数接收onInvalidate参数) |
1. 依赖追踪方式对比(最核心区别)
watch:显式指定监听源
必须明确告诉watch要监听哪个 / 哪些响应式数据,未指定的不会被追踪:
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
const name = ref('Vue')
// 显式监听 count,name 变化不会触发此 watch
watch(count, (newVal, oldVal) => {
console.log(`count从${oldVal}变成${newVal}`)
})
// 显式监听多个源(数组形式)
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
console.log(`count变化:${oldCount}→${newCount},name变化:${oldName}→${newName}`)
})
</script>
watchEffect:隐式追踪依赖
无需指定监听源,函数内用到的所有响应式数据都会被自动追踪:
<script setup>
import { ref, watchEffect } from 'vue'
const count = ref(0)
const name = ref('Vue')
// 自动追踪 count 和 name(函数内用到了这两个响应式数据)
watchEffect(() => {
console.log(`当前count:${count.value},当前name:${name.value}`)
})
// 首次执行输出:当前count:0,当前name:Vue(立即执行特性)
// 当count或name变化时,会重新执行
</script>
2. 执行时机对比
watch:默认懒执行
只有监听源发生变化时才会执行,组件挂载时不会主动执行:
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
// 组件挂载时不会执行,只有 count 变化时才执行
watch(count, (newVal) => {
console.log(`count变化了:${newVal}`)
})
// 若需要立即执行,需手动设置 immediate: true
watch(count, (newVal) => {
console.log(`count(立即执行):${newVal}`)
}, { immediate: true }) // 组件挂载时执行一次,之后 count 变化再执行
</script>
watchEffect:默认立即执行
组件挂载时会先执行一次(用于收集依赖),之后依赖变化时再执行:
<script setup>
import { ref, watchEffect } from 'vue'
const count = ref(0)
// 组件挂载时立即执行一次,之后 count 变化时再执行
watchEffect(() => {
console.log(`count:${count.value}`)
})
// 输出:count:0(挂载时)→ count:1(当count++时)
</script>
3. 新旧值访问对比
watch:支持访问新旧值
回调函数的第一个参数是新值,第二个参数是旧值(引用类型需注意浅对比):
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
watch(count, (newVal, oldVal) => {
console.log(`旧值:${oldVal},新值:${newVal}`) // 例如:旧值:0,新值:1
})
// 监听对象(需注意:默认是浅监听,对象内部属性变化需开启 deep: true)
const user = ref({ name: 'Vue', age: 3 })
watch(user, (newUser, oldUser) => {
console.log('user变化了', newUser, oldUser)
}, { deep: true }) // 深度监听对象内部属性
</script>
watchEffect:不支持访问旧值
只能访问当前最新的响应式数据,无法获取变化前的值:
<script setup>
import { ref, watchEffect } from 'vue'
const count = ref(0)
watchEffect(() => {
// 只能拿到当前值,拿不到变化前的旧值
console.log(`当前count:${count.value}`)
})
</script>
三、使用技巧与场景选择
1. 什么时候用 watch?
- 需要访问新旧值时(如对比变化前后的数据);
- 需要明确控制监听源(只监听特定数据,避免无关依赖触发);
- 需要延迟执行(如使用
flush: 'post'在 DOM 更新后执行); - 需要深度监听对象 / 数组且需要精细控制时;
- 需要懒执行(仅在数据变化时执行,避免初始化开销)。
实用技巧:
-
监听对象单个属性:用函数返回具体属性(避免深度监听整个对象,提升性能):
<script setup> import { ref, watch } from 'vue' const user = ref({ name: 'Vue', age: 3 }) // 只监听 user.name,无需深度监听整个对象 watch(() => user.value.name, (newName) => { console.log(`用户名变化:${newName}`) }) </script> -
延迟执行(DOM 更新后):使用
flush: 'post':watch(count, () => { // 此时DOM已经更新,可以获取更新后的DOM元素 console.log(document.getElementById('count').innerText) }, { flush: 'post' })
2. 什么时候用 watchEffect?
- 副作用依赖多个响应式数据,且无需区分哪个变化(自动追踪所有用到的);
- 需要立即执行副作用(如初始化时请求数据,之后依赖变化重新请求);
- 不需要访问旧值,只关注当前最新状态;
- 写法简洁优先(减少代码量)。
实用技巧:
-
清除副作用(如取消请求、清除定时器):使用
onInvalidate回调(组件卸载或副作用重新执行前触发):<script setup> import { ref, watchEffect } from 'vue' import axios from 'axios' const id = ref(1) watchEffect((onInvalidate) => { // 发起请求 const request = axios.get(`/api/data/${id.value}`) // 清除函数:组件卸载或 id 变化时触发 onInvalidate(() => { request.cancel() // 取消请求 }) }) </script> -
延迟执行(同 watch):使用
flush: 'post':watchEffect(() => { // DOM更新后执行 console.log(document.getElementById('count').innerText) }, { flush: 'post' })
3. 共同技巧:停止监听
watch和watchEffect都返回一个停止函数,调用后会停止监听(组件卸载时会自动停止,无需手动调用):
<script setup>
import { ref, watch, watchEffect } from 'vue'
const count = ref(0)
// watch 停止监听
const stopWatch = watch(count, (newVal) => {
console.log(`count:${newVal}`)
})
// watchEffect 停止监听
const stopWatchEffect = watchEffect(() => {
console.log(`count:${count.value}`)
})
// 手动停止监听(如某个条件满足时)
const stopAll = () => {
stopWatch()
stopWatchEffect()
}
</script>
总结
核心区别
- 依赖追踪:
watch显式指定源,watchEffect隐式追踪; - 执行时机:
watch默认懒执行,watchEffect默认立即执行; - 新旧值:
watch支持访问新旧值,watchEffect不支持。
场景选择
- 需旧值、明确监听源、精细控制 → 用
watch; - 多依赖、立即执行、简洁优先 → 用
watchEffect。
关键技巧
watch监听对象单个属性时,用函数返回(避免深度监听);watchEffect需清除副作用时,使用onInvalidate;- 两者都可通过返回的停止函数手动停止监听,组件卸载时自动停止。
1456

被折叠的 条评论
为什么被折叠?



