使用vueuse对watch封装后的3个方法来提高效率
vue开发中对watch的使用比较多的几个场景有:当监听值为真值时执行回调;当监听的值变化时只触发一次回调;还有当监听的值变化时最多触发多少次回调等。本文要介绍的whenever、watchOnce、watchAtMost是vueuse对watch的封装,可以用在我们以上提到的场景下并且减少开发工作量,一起了解一下吧~
1.whenever:当watch的值为真值时
在开发vue项目时,我们总会遇到当watch的数据为真值时则执行某些操作的场景,笔者参与开发的项目中就有很多这样的场景,如下代码展示了一些例子:
watch: {
url (val) {
if(val){
this.uploadAction = window._CONFIG['domianURL']+val
}
}
},
watch: {
visible(val) {
if (val) {
this.innerFullscreen = this.fullscreen
}
},
},
watch:{
username(val){
if(val){
this.loadDepartList()
}
}
},
上面代码的第一例子是监听url变化然后修改上传地址;第二个例子是监听visible变化然后设置全屏;第三个例子是监听用户名变化,发起网络请求。上面代码是vue2中的写法,举例说明vue3写法:
watch(username, (username) => {
if(username) {
loadDepartList()
}
})
不管是vue2还是vue3中的写法,我们发现这样的代码片段的共同点是在watch的回调中曾if条件语句进行判断。是否可以把共性用法进行抽象封装为一个新的方法呢?答案是可以的,whenever 就是对当watch的值为真值时要执行回调函数这样场景的封装,下面我们一起研究一下其使用以及源码。
1.1 示例
笔者根据whenever的介绍写了一个小demo:
<script setup lang="ts">
import { whenever } from '@vueuse/core'
import {ref} from 'vue'
const source = ref(false)
whenever(
source,
(value) => {
console.log('当前值',value)
},
)
const clickedFn = () => {
source.value = !source.value
}
</script>
<template>
<div>{{source}}</div>
<button @click="clickedFn">
点击按钮
</button>
</template>
如上代码所示:引入了whenever对source值监听,只有当source值为真值时(这里是true)才输出当前值。按钮的点击事件用于切换source的值。代码运行结果如下:
点击切换source的值,只有当source的值为真值(这里是true)时则打印出当前的source值,也就是true。
1.2 源码分析
whenever的源码非常简单只有十几行:
import type { WatchCallback, WatchOptions, WatchSource } from 'vue-demi'
import { watch } from 'vue-demi'
export function whenever<T>(source: WatchSource<T | false | null | undefined>, cb: WatchCallback<T>, options?: WatchOptions) {
return watch(
source,
(v, ov, onInvalidate) => {
if (v)
cb(v, ov, onInvalidate)
},
options,
)
}
whenever是通过对watch的回调函数执行进行封装,判断source的值是否为真值,满足条件则执行目标回调cb。通过watch的类型声明我们知道了wath的回调函数参数分为新的值、旧的值以及一个回调。回调函数的参数(v, ov, onInvalidate)引起我们注意的是onInvalidate:
function watch<T>(
source: WatcherSource<T>,
callback: (
value: T,
oldValue: T,
onInvalidate: InvalidateCbRegistrator
) => void,
options?: WatchOptions
): StopHandle
callback的第三个参数onInvalidate用于清除副作用。什么时副作用呢?副作用是一个使用到响应式变量的函数的包裹器,在函数调用之前就启动跟踪了。vue知道哪个副作用在何时运行,并能够在需要时再次执行它。
而vue3文档中是这样解释onInvalidate的:有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除 (即完成之前状态已改变了) 。所以侦听副作用传入的函数可以接收一个 onInvalidate 函数作入参,用来注册清理失效时的回调。更多关于onInvalidate的内容请阅读文档。
1.3 总结
whenever核心在于在回调函数中增加if条件语句判断观察值是否为真值。相信通过使用whenever可以帮助我们少写不少的if语句。本文的demo代码已经上传至github, 欢迎您clone并亲自体验whenever的使用。
2. watchOnce:只触发一次回调
我们知道vue为v-on提供了许多事件修饰符,比如.stop、.prevent、.once。其中.once用于保证点击事件只触发一次。只触发一次是一种很常见的场景,例如用户第一次点击某个按钮时给用户一些提示信息,之后点击则不再提示了。如果遇到了只让watch回调触发一次的情况,您怎么做呢?vueuse提供的watchOnce是只让回调触发一次的watch,今天我们一起学习其用法和源码。
2.1 示例
根据官方文档的介绍写了一个demo:
<script setup lang="ts">
import { watchOnce } from '@vueuse/core'
import {ref} from 'vue'
const source = ref(0)
watchOnce(
source,
(value) => {
console.log('变化了', value)
},
)
const clickedFn = () => {
source.value ++
}
</script>
<template>
<div>{{source}}</div>
<button @click="clickedFn">
点击按钮
</button>
</template>
点击按钮,source加1,使用watchOnce监听sorce变化。代码运行效果如下图所示:
点击了按钮5次但是只触发了1次回调。watchOnce用起来是不是很便捷呢?如果我们自己实现只让回调中的代码(这里时console.log)执行一次的功能,您可能会想到额外使用一个计数器就搞定了,例如下面的代码:
<script setup lang="ts">
import {ref, watch} from 'vue'
const source = ref(0)
const count = ref(0)
watch(
source,
(value) => {
if (count.value === 0) {
console.log('变化了', value)
count.value++
}
},
)
const clickedFn = () => {
source.value ++
}
</script>
<template>
<div>{{source}}</div>
<button @click="clickedFn">
点击按钮
</button>
</template>
这样的代码也能够实现要求的功能,但是我们不得不声明额外的计数器变量count,还需要在watch的回调中增加if语句。这样写一个两个功能没问题,如果有很多这样的场景,那么使用watchOnce就很有必要了。下面我们研究一下watchOnce的源码。
2.2 源码
watchOnce源码同样很短,很易于理解:
import { nextTick, watch } from 'vue-demi'
// implementation
export function watchOnce<Immediate extends Readonly<boolean> = false>(
source: any,
cb: any,
options?: WatchOptions<Immediate>,
): void {
const stop = watch(source, (...args) => {
nextTick(() => stop())
return cb(...args)
}, options)
}
可以看到watchOnce的实现主要是使用了nextTick API,在nextTick的回调中调用停止监听的stop方法。简单提一下nextTick,它是将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。
2.3 总结
一句话总结watchOnce的核心原理:执行一次watch的回调之后,就使用nextTick调用取消监听的方法。本文的demo代码已经上传至github, 欢迎您clone并亲自体验watchOnce的使用。
3.watchAtMost:规定最多触发多少次
在之前的内容中我们介绍了使用watchOnce可以使watch的回调只触发一次 。如果要求watch的回调函数触发一定次数就不再触发了则该如何实现呢?
比如这样的场景:有一个投票页面可以给参与评选者投票,每个评选者名字后面都有投票按钮,但是要求投票者最多只能投3票。实现这样的需求并不难,方法也很多。比如你可以在投票对应的方法中使用计数器,如果计数器的值大于等于3则方法返回。当然也可以使用watch来实现,投票方法中对计数器进行累加,watch判断计数器的值,如果小于等于3则执行回调。代码大概这样:
<script setup lang="ts">
import {ref,watch} from 'vue'
const counter = ref(0)
const clickedFn = () => {
counter.value ++
}
const vote = () => {
//...
}
watch(
counter,
(value) => {
if(value <= 3) {
vote()
}
},
)
</script>
这段代码中我们声明了计数器变量counter,并在watch的回调中对counter进行判断从而控制vote方法的执行次数。如果使用watchAtMost,那么我们就不用声明计数器和在watch回调中判断了。watchAtMost规定了最多触发多少次watch的回调。使用方法和watch一样,只不过在选项参数中多了一个额外的参数count规定回调函数执行的次数。一旦达到了次数count, watch将会自动停止监听。下面我们看一下watchAtMost的使用方法。
3.1 示例
根据官网介绍的使用方法,笔者写了如下的测试demo:
<script setup lang="ts">
import { watchAtMost } from '@vueuse/core'
import {ref} from 'vue'
const source = ref(0)
watchAtMost(
source,
(value) => {
console.log('变化了', value)
},
{
count: 4
}
)
const clickedFn = () => {
source.value ++
}
</script>
<template>
<div>{{source}}</div>
<button @click="clickedFn">
点击按钮
</button>
</template>
点击按钮对source进行累加,使用watchAtMost监听source变化,规定了watch最多监听source值的4次变化。和上面投票的代码比起来是不是更加简洁呢?代码运行效果如下图所示:
按钮点击次数为5,但是只输出了4次。下面我们分析一下watchAtMost的源码。
3.2 源码
import { nextTick, ref, unref } from 'vue-demi'
import { watchWithFilter } from '../watchWithFilter'
// implementation
export function watchAtMost<Immediate extends Readonly<boolean> = false>(
source: any,
cb: any,
options: WatchAtMostOptions<Immediate>,
): WatchAtMostReturn {
const {
count,
...watchOptions
} = options
const current = ref(0)
const stop = watchWithFilter(
source,
(...args) => {
current.value += 1
if (current.value >= unref(count))
nextTick(() => stop())
cb(...args)
},
watchOptions,
)
return { count: current, stop }
}
首先是参数处理:从options中解构出count,并使用watchOptions收集其他选项。定义current记录回调执行次数,每次执行回调时自增。使用watchWithFilter来对source监听,之所以使用watchWithFilter而没有使用watch的原因使因为使用watchWithFilter可以支持对回调的控制。回调函数中对当前执行次数进行判断,如果大于等于count则停止监听。watchAtMost和watchOnce的共同点是都是在nextTick的回调中调用了停止监听的stop方法。
3.3 总结
watchAtMost在内部使用一个current变量记录了watch回调的执行次数,当达到count时调用取消监听的方法。本文的demo代码已经上传至github, 欢迎您clone并亲自体验watchAtMost的使用。
转载自:https://juejin.cn/post/7117184853644148773