1.watch
基本用法
在vue中需要根据某些响应式数据的变化而进行特定复杂逻辑(网络请求、定时任务、操作dom等)的处理时,便可以采用watch函数:
const watchState = ref(0)
watch(watchState, (newValue, oldValue) => {
// 复杂逻辑处理
})
watch的简单用法如上图所述,当响应式数据watchState变化的时候,传入watch的回调函数便会自动执行,该回调函数可以接收到watchState变化前后的值。
watch函数的第一参数即监听的数据源支持以下几种格式:
- ref
- rective
- 计算属性
- getter
- 上述三种形式组合成的数组
// 写法1
const a = ref(0)
watch(a, (newValue, oldValue) => {})
// 写法2
const b = reactive({name: '张三', age: 11})
// 默认开启了deep的深度监听器,所有嵌套属性改变都会触发
watch(b, (newValue, oldValue) => {})
// 写法3
const comp = computed(() => {
...
})
watch(comp, (newVal, oldVal) => {})
// 写法4
const getter = () => b.name
watch(getter, (newValue, oldValue) => {})
// 下面的写法是错误的,因为该写法相当于直接把一个字符串放到了watch里面,若想监听reactive对象的某个属性,需要采用上面的getter写法
watch(b.name, () => {})
// 写法5
const arr = [a, b, comp, getter]
watch(arr, ([newA, newB, newComp, newGetter], [oldA, oldB, oldComp, oldGetter]) => {})
副作用清理
watch的回调函数除了接收监听数据的新值和旧值,还可以接受一个清理函数onCleanUp,该函数主要用于清理回调函数可能产生的副作用,watch也会返回一个函数stop,可帮助用户自定义时刻停止watch监听逻辑
const a = ref(1)
const stop = watch(a, (newVal, oldVal, onCleanUp) => {
const intervalId = setInterval(() => {
console.log(newVal)
}, 1000)
onCleanUp(function clear() {
console.log('清理')
clearInterval(intervalId)
})
})
如上述代码展示,回调函数接收了三个参数,其中第三个参数便是清理函数,在回调函数中声明了一个定时任务,然后调用了onCleanUp并传入了清理定时任务的clear函数,clear函数的调用时机包括:
- 回调函数下一次执行前自动调用
- 在同步语句中声明的watch会在组件卸载时自动调用
- 手动调用stop函数
第一个时机表示clear函数会在回调函数执行的第n次之前调用(n=2, 3, 4....),第二个时机需要特别注意同步语句,与之相对应的即在异步语句中声明的watch在组件卸载的时候不会自动清理
<script setup>
// watch1
watch(source, (newVal, oldVal, onCleanUp) => {
onCleanUp(() => {}) // 自动调用
})
// watch2
Promise.resolve().then(() => {
watch(source, (newVal, oldVal, onCleanUp) => {
onCleanUp(() => {}) // 不会自动调用
})
})
//watch3
setTimeout(() => {
watch(source, (newVal, oldVal, onCleanUp) => {
onCleanUp(() => {}) // 不会自动调用
})
})
</script>
第三个时机的手动清理比较好理解,例如可以在用户触发某个点击事件的时候调用stop函数,触发停止监听并执行清理功能
配置项
watch函数可以传递三个参数,分别是监听的数据源、回调函数以及配置项,其中配置项支持参数包括:deep、immediate、flush
深度监听
其中deep表示是否开启深度监听,针对一个reactive对象,如何采用watch对其进行监听,默认会开启深度监听,一个返回响应式对象的 getter 函数,只有在返回不同的对象时,才会触发回调,只有加上了deep: true的配置才会转换成深度监听
watch(() => state.obj, () => {}, {
deep: true
})
立即执行
watch默认是懒执行的,仅当数据源变化时,才会执行回调,如果希望在建立监听器的时候,立即执行一次回调函数,可以通过设置immediate: true来实现回调函数的立即执行
watch(source, () => {}, { immediate: true })
执行时机
watch回调函数会被批量处理以避免重复调用,如下的代码,触发了changeRefA事件之后,a的值被连续更改了三次,但watch回调函数中的打印只会执行一次,如果希望watch的回调同步执行可以设置参数flush: 'sync'
const a = ref(1)
watch(a, () => {
console.log('批量执行') // 打印一次
})
watch(a, val => {
console.log('同步执行') // 打印三次
}, { flush: 'sync' })
// 点击事件,修改a的值
const changeRefA = () => {
a.value++
a.value++
a.value++
}
按照vue官方文档说法:默认情况下,侦听器回调会在父组件更新 (如有) 之后、所属组件的 DOM 更新之前被调用。这意味着如果你尝试在侦听器回调中访问所属组件的 DOM,那么 DOM 将处于更新前的状态。
需要注意的点是:默认情况下,在回调函数中获取的所属组件的dom是更新之前的dom,如果希望获取dom更新之后的状态,可以设置参数flush: 'post',可以获得类似nextTick的效果
<template>
<div>
<button @click='showButton'>show button</button>
<button v-if='show' id='btn'></button>
</div>
</template>
<script setup>
const show = ref(false)
const showButton = () => {
show.value = true
}
watch(show, (val) => {
if (val) {
document.getElementById('btn') // 点击show button按钮,触发该回调函数执行,但此时的dom为null
}
})
watch(show, (val) => {
if (val) {
document.getElementById('btn') // 此时的值为页面button的dom
}
}, { flush: 'post' })
</script>
2.watchEffect
基本用法
watchEffect允许自动追踪回调函数中的响应式数据,写法如下
watch(params, async val => {
await request.post('', params)
})
// 不需要特定写明监听params,param改变,会自动重新请求数据
watchEffect(async () => {
await request.post('', params)
})
watchEffect与watch的懒执行不同,默认会执行一次,相当于默认设置了immediate: true
副作用清理
watchEffect的副作用清理主要由回调函数的参数完成,方式和watch相同,清理函数的调用时机也和watch相同
watchEffect((onCleanUp) => {
onCleanUp(() => {
// 清理逻辑
})
})
配置项
watchEffect的配置项参数主要是flush,watchEffect默认实现了立即执行和对使用过的属性自动追踪的效果,因此,无需immediate和deep参数。针对flush参数,watchEffect实现的效果和watch相同,vue还提供了对应的简写api
watchEffect(callback, { flush: 'post' })
watchPostEffect(callback)
watchEffect(callback, { flush: 'sync' })
watchSyncEffect(callback)
watch vs watchEffect
相同点
主要功能相同,都可以响应地执行回调函数,完成复杂逻辑的处理
不同点
- watch默认是懒执行的,watchEffect会在初始定义时执行一次
- watch只会追踪明确定义的数据源,不会追踪回调函数中使用到的数据源,可以更精确地控制回调函数的执行时机;watchEffect不需要明确定义需要追踪的数据源,可以自动追踪回调函数中使用到的响应式数据,代码更加简洁,但依赖关系不如watch明确