前言🌟
本文诣在帮大家彻底梳理Vue3中watch的用法。
首先,介绍Vue3的watch API的基本使用,然后介绍了watch在侦听单个数据源、多个数据源的注意事项,最后通过对比得出watch侦听引用类型数据源的最佳实践。
一、API介绍
watch(WatcherSource, Callback, [WatchOptions])
type WatcherSource<T> = Ref<T> | (() => T)
interface WatchOptions extends WatchEffectOptions {
deep?: boolean // 默认:false
immediate?: boolean // 默认:false
flush?: string // 默认:'pre'
}
参数说明:
WatcherSource: 用于指定要侦听的响应式数据源。侦听器数据源可以是返回值的 getter 函数,也可以直接是 ref。
Callback: 执行的回调函数,可依次接收当前值newValue,先前值oldValue作为入参。
WatchOptions:deep、immediate、flush可选。
- 当需要对响应式对象进行深度监听时,设置
deep: true。 - 默认情况下watch是惰性的,当我们设置
immediate: true时,watch会在初始化时立即执行回调函数。 flush选项可以更好地控制回调的时间。它可设置为pre、post或sync。- 默认值是
pre,指定的回调应该在渲染前被调用。 post值是可以用来将回调推迟到渲染之后的。如果回调需要通过$refs访问更新的 DOM 或子组件,那么则使用该值。- 如果
flush被设置为sync,一旦值发生了变化,回调将被同步调用(少用,影响性能)。
- 默认值是
二、侦听单个数据源及停止侦听
<script setup>
import { watch, ref, reactive } from 'vue'
// 侦听一个 getter
const person = reactive({name: '小松菜奈'})
watch(
() => person.name,
(value, oldValue) => {
console.log(value, oldValue)
}, {immediate:true}
)
person.name = '有村架纯'
// 直接侦听ref
const ageRef = ref(16)
const stopAgeWatcher = watch(ageRef, (value, oldValue) => {
console.log(value, oldValue)
if (value > 18) {
stopAgeWatcher() // 当ageRef大于18,停止侦听
}
})
const changeAge = () => {
ageRef.value += 1
}
</script>
现象:配置了immediate:true的watch,在初始化时触发了一次watch的回调。我们连续点击增加年龄,当年龄的当前值大于18时,watch停止了侦听。
结论:侦听器数据源可以是返回值的 getter 函数,也可以直接是 ref。我们可以利用给watch函数取名字,然后通过执行名字()函数来停止侦听。
三、监听多个数据源
<script setup>
import {ref, watch, nextTick} from 'vue'
const name = ref('小松菜奈')
const age = ref(25)
watch([name, age], ([name, age], [prevName, prevAge]) => {
console.log('newName', name, 'oldName', prevName)
console.log('newAge', age, 'oldAge', prevAge)
})
// 如果你在同一个函数里同时改变这些被侦听的来源,侦听器只会执行一次
const change1 = () => {
name.value = '有村架纯'
age.value += 2
}
// 用 nextTick 等待侦听器在下一步改变之前运行,侦听器执行了两次
const change2 = async () => {
name.value = '新垣结衣'
await nextTick()
age.value += 2
}
</script>
现象:以上,当我们在同一个函数里同时改变name和age两个侦听源,watch的回调函数只触发了一次;当我们在name和age的改变之间增加了一个nextTick,watch回调函数触发了两次。
结论:我们可以通过watch侦听多个数据源的变化。如果在同一个函数里同时改变这些被侦听的来源,侦听器只会执行一次。若要使侦听器执行多次,我们可以利用 nextTick ,等待侦听器在下一步改变之前运行。
四、侦听引用对象(数组Array或对象Object)
<template>
<div>
<div>ref定义数组:{{arrayRef}}</div>
<div>reactive定义数组:{{arrayReactive}}</div>
</div>
<div>
<button @click="changeArrayRef">改变ref定义数组第一项</button>
<button @click="changeArrayReactive">改变reactive定义数组第一项</button>
</div>
</template>
<script setup>
import {ref, reactive, watch} from 'vue'
const arrayRef = ref([1, 2, 3, 4])
const arrayReactive = reactive([1, 2, 3, 4])
// ref not deep, 不能深度侦听
const arrayRefWatch = watch(arrayRef, (newValue, oldValue) => {
console.log('newArrayRefWatch', newValue, 'oldArrayRefWatch', oldValue)
})
// ref deep, 深度侦听,新旧值一样
const arrayRefDeepWatch = watch(arrayRef, (newValue, oldValue) => {
console.log('newArrayRefDeepWatch', newValue, 'oldArrayRefDeepWatch', oldValue)
}, {deep: true})
// ref deep, getter形式 , 新旧值不一样
const arrayRefDeepGetterWatch = watch(() => [...arrayRef.value], (newValue, oldValue) => {
console.log('newArrayRefDeepGetterWatch', newValue, 'oldArrayRefDeepGetterWatch', oldValue)
})
// reactive,默认深度监听,可以不设置deep:true, 新旧值一样
const arrayReactiveWatch = watch(arrayReactive, (newValue, oldValue) => {
console.log('newArrayReactiveWatch', newValue, 'oldArrayReactiveWatch', oldValue)
})
// reactive,getter形式 , 新旧值不一样
const arrayReactiveGetterWatch = watch(() => [...arrayReactive], (newValue, oldValue) => {
console.log('newArrayReactiveFuncWatch', newValue, 'oldArrayReactiveFuncWatch', oldValue)
})
const changeArrayRef = () => {
arrayRef.value[0] = 3
}
const changeArrayReactive = () => {
arrayReactive[0] = 6
}
</script>
现象:当将引用对象采用ref形式定义时,如果不加上deep:true,watch是侦听不到值的变化的;而加上deep:true,watch可以侦听到数据的变化,但是当前值和先前值一样,即不能获取先前值。当将引用对象采用reactive形式定义时,不作任何处理,watch可以侦听到数据的变化,但是当前值和先前值一样。两种定义下,把watch的数据源写成getter函数的形式并进行深拷贝返回,可以在watch回调中同时获得当前值和先前值。
结论:
当我们使用watch侦听引用对象时
- 若使用
ref定义的引用对象:- 只要获取当前值,watch第一个参数直接写成数据源,另外需要加上
deep:true选项 - 若要获取当前值和先前值,需要把数据源写成getter函数的形式,并且需对数据源进行深拷贝
- 只要获取当前值,watch第一个参数直接写成数据源,另外需要加上
- 若使用
reactive定义的引用对象:- 只要获取当前值,watch第一个参数直接写成数据源,可以不加
deep:true选项 - 若要获取当前值和先前值,需要把数据源写成getter函数的形式,并且需对数据源进行深拷贝
- 只要获取当前值,watch第一个参数直接写成数据源,可以不加
🎉🎉🎉🎉🎉