likes
comments
collection
share

使用vueuse对watch封装后的3个方法来提高效率

作者站长头像
站长
· 阅读数 46

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的值。代码运行结果如下:

使用vueuse对watch封装后的3个方法来提高效率

点击切换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变化。代码运行效果如下图所示:

使用vueuse对watch封装后的3个方法来提高效率

点击了按钮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次变化。和上面投票的代码比起来是不是更加简洁呢?代码运行效果如下图所示:

使用vueuse对watch封装后的3个方法来提高效率

按钮点击次数为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
评论
请登录