在 Vue 3 的响应式系统中,watch 和 watchEffect 是两个至关重要的工具,用于观察和响应数据的变化。但你是否清楚它们各自的适用场景?什么时候该用 watch,什么时候又该用 watchEffect 呢?这篇指南将通过具体的代码示例,帮你理清两者的核心区别与最佳实践。
1. watch 的详细用法
watch API 提供了一种显式、可配置的方式来侦听一个或多个响应式数据源的变化。
监听 ref 定义的响应式数据
对于 ref 创建的响应式数据,直接将其作为 watch 的第一个参数即可。
<template>
<div>
<div>值:{{count}}</div>
<button @click="add">改变值</button>
</div>
</template>
<script>
import { ref, watch } from 'vue';
export default {
setup(){
const count = ref(0);
const add = () => {
count.value ++
};
watch(count,(newVal,oldVal) => {
console.log('值改变了',newVal,oldVal)
})
return {
count,
add,
}
}
}
</script>
监听 reactive 定义的响应式数据
当监听整个 reactive 对象时,watch 会默认启用深度侦听。
<template>
<div>
<div>{{obj.name}}</div>
<div>{{obj.age}}</div>
<button @click="changeName">改变值</button>
</div>
</template>
<script>
import { reactive, watch } from 'vue';
export default {
setup(){
const obj = reactive({
name:'zs',
age:14
});
const changeName = () => {
obj.name = 'ls';
};
watch(obj,(newVal,oldVal) => {
console.log('值改变了',newVal,oldVal)
})
return {
obj,
changeName,
}
}
}
</script>
监听多个响应式数据
通过将多个数据源放入数组,可以同时侦听它们的变化。
<template>
<div>
<div>{{obj.name}}</div>
<div>{{obj.age}}</div>
<div>{{count}}</div>
<button @click="changeName">改变值</button>
</div>
</template>
<script>
import { reactive, ref, watch } from 'vue';
export default {
setup(){
const count = ref(0);
const obj = reactive({
name:'zs',
age:14
});
const changeName = () => {
obj.name = 'ls';
};
watch([count,obj],() => {
console.log('监听的多个数据改变了')
})
return {
obj,
count,
changeName,
}
}
}
</script>
监听对象中某个属性的变化
有时你只需要关注对象内的一个特定属性,这时可以使用 getter 函数作为数据源。
<template>
<div>
<div>{{obj.name}}</div>
<div>{{obj.age}}</div>
<button @click="changeName">改变值</button>
</div>
</template>
<script>
import { reactive, watch } from 'vue';
export default {
setup(){
const obj = reactive({
name:'zs',
age:14
});
const changeName = () => {
obj.name = 'ls';
};
watch(() => obj.name,() => {
console.log('监听的obj.name改变了')
})
return {
obj,
changeName,
}
}
}
</script>
通过第三个参数配置对象,你可以控制 watch 的深度侦听行为以及是否在初始化时立即执行回调。
<template>
<div>
<div>{{obj.brand.name}}</div>
<button @click="changeBrandName">改变值</button>
</div>
</template>
<script>
import { reactive, watch } from 'vue';
export default {
setup(){
const obj = reactive({
name:'zs',
age:14,
brand:{
id:1,
name:'宝马'
}
});
const changeBrandName = () => {
obj.brand.name = '奔驰';
};
watch(() => obj.brand,() => {
console.log('监听的obj.brand.name改变了')
},{
deep:true,
immediate:true,
})
return {
obj,
changeBrandName,
}
}
}
</script>
2. watchEffect 的用法
watchEffect 是另一种响应式副作用的工具,它的心智模型与 watch 有所不同。
基本用法
watchEffect 会自动追踪其回调函数内部所访问的所有响应式依赖,并在它们发生变化时立即重新执行。
<template>
<div>
<input type="text" v-model="obj.name">
</div>
</template>
<script>
import { reactive, watchEffect } from 'vue';
export default {
setup(){
let obj = reactive({
name:'zs'
});
watchEffect(() => {
console.log('name:',obj.name)
})
return {
obj
}
}
}
</script>
停止侦听
watchEffect 会返回一个停止函数,调用它可以手动停止侦听。
<template>
<div>
<input type="text" v-model="obj.name">
<button @click="stopWatchEffect">停止监听</button>
</div>
</template>
<script>
import { reactive, watchEffect } from 'vue';
export default {
setup(){
let obj = reactive({
name:'zs'
});
const stop = watchEffect(() => {
console.log('name:',obj.name)
})
const stopWatchEffect = () => {
console.log('停止监听')
stop();
}
return {
obj,
stopWatchEffect,
}
}
}
</script>
3. 核心总结与选择指南
了解了两者的具体用法后,我们再来系统性地对比一下它们的核心特点,这有助于你在实际开发中做出正确的选择。
watch 的核心特点
- 有惰性:默认情况下,不会在侦听器创建时立即执行回调,只有数据源变化后才执行。
- 更加具体:需要显式指定要侦听的单个或多个数据源。
- 可访问变化前后的值:回调函数会接收
newValue 和 oldValue 作为参数。
- 高度可配置:
immediate:控制是否立即执行。
deep:控制是否进行深度侦听。
watchEffect 的核心特点
- 非惰性:创建后会立即执行一次,以收集依赖。
- 更加抽象和简洁:自动追踪回调函数内所有被使用的响应式依赖,无需手动声明。
- 不可直接访问旧值:回调函数中没有旧值参数,只能获取到当前的最新值。
- 自动停止:当组件卸载时,侦听器会自动停止。
如何选择?
- 使用
watch 的场景:当你需要明确知道是哪个值发生了变化,并且需要用到变化前后的值进行比较或逻辑处理时;或者当你需要惰性执行、深度监听等精细化控制时。
- 使用
watchEffect 的场景:当你需要执行的副作用逻辑依赖于多个响应式状态,并且你更关心逻辑执行(基于当前最新值)而非具体哪个值变化时。它能简化代码,自动管理依赖关系。
watch API 参数详解
watch 的函数签名非常清晰:watch(source, callback, options)。
第一个参数(数据源 source) 可以是:
- 一个返回值的 getter 函数
- 一个
ref
- 一个
reactive 对象
- 以上类型组成的数组
第二个参数(回调函数 callback):
(newValue, oldValue, onCleanup) => {
// 副作用清理
onCleanup(() => { /* 清理逻辑 */ });
}
第三个参数(配置选项 options):
interface WatchOptions {
immediate?: boolean // 默认 false
deep?: boolean // 默认 false
flush?: 'pre' | 'post' | 'sync' // 默认 'pre'
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
once?: boolean // Vue 3.4+ 默认 false
}
掌握 watch 和 watchEffect 的差异,能让你在构建 Vue.js 应用时更加得心应手,根据不同的业务场景选择最合适的响应式侦听方案。希望这篇指南能帮助你更深入地理解 Vue 3 的响应式副作用系统。如果你想与其他开发者交流更多前端框架的实战心得,欢迎来云栈社区分享你的见解。