更好地对watch的回调函数进行控制:watchWithFilter源码解析

605 阅读3分钟

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿

防抖和节流是我们常遇到的场景,例如使用防抖处理连续点击、使用节流处理滚动条滚动事件。那么问题来了,你有没有遇到需要对watch的回调函数进行控制的场景呢?如果有,那么推荐你学习一下watchWithFilter

watchWithFilter 是一个有事件过滤控制器(EventFilter)的watch。和watch类似,但是提供了额外的事件过滤器(eventFilter),并将之应用到watch的回调函数中。本文分析了watchWithFilter的使用方法以及源码实现,欢迎阅读了解。

1. 示例

在本小节,我们首先通过官方示例demo来了解watchWithFilter的使用,接下来还会演示2个为了完成同样功能不用watchWithFilter而自己手动实现的例子。通过对比示例代码理解watchWithFilter的优势。

1.1 官网示例

结合官方文档给出的示例写了一个demo, 代码如下:

<script setup lang="ts">
  import { debounceFilter, watchWithFilter } from '@vueuse/core'
  import {ref} from 'vue'
  
  const source = ref(0)
  
  watchWithFilter(
    source,
    () => { console.log('changed!') }, // callback will be called in 500ms debounced manner
    {
      eventFilter: debounceFilter(500), // throttledFilter, pausabledFilter or custom filters
    },
  )
  
  const clickedFn = () => {
    source.value++
  }
  
</script>

<template>
  <div>{{source}}</div>
  <button @click="clickedFn">
    点击按钮
  </button>
</template>

如上代码所示,引入了watchWithFilter和debounceFilter,使用debounceFilter对回调进行防抖,延时事件为500毫秒。代码运行效果如下:

点击了6次按钮但是只触发了2次watch回调。下面看一下使用watch结合手写的防抖函数处理过的函数的写法:

1.2 手动控制方法1

<script setup lang="ts">
  import { createFilterWrapper, debounceFilter } from '@vueuse/core'
  import {ref, watch} from 'vue'
  import type { Ref } from 'vue'
  
  type MaybeRef<T> = T | Ref<T>
  type FunctionArgs<Args extends any[] = any[], Return = void> = (...args: Args) => Return
  
  const source = ref(0)
  
  const callback = () => console.log('changed!')
  
  // 手写一个防抖
  const debouncedFn = (ms:MaybeRef<number>, fn:FunctionArgs) => {
    return createFilterWrapper(debounceFilter(ms),  fn)
  }
  
  const watchCallback = debouncedFn(500, callback)
  
  watch(
    source,
    () => {
      watchCallback()
    },
  )
  
  const clickedFn = () => {
    source.value++
  }
  
</script>

<template>
  <div>{{source}}</div>
  <button @click="clickedFn">
    点击按钮
  </button>
</template>

这里引入了createFilterWrapper, debounceFilter并使用它们写了一个防抖函数debouncedFn,在watch的回调中调用由debouncedFn返回的防抖回调函数watchCallback。代码运行效果如下:

点击了3次按钮但是只触发了1次watch回调。当然也可以使用useDebounceFn使得上面的代码更加简化:

1.3 手动控制方法2

<script setup lang="ts">
import { useDebounceFn } from '@vueuse/core'
import {ref, watch} from 'vue'

const source = ref(0)
const callback = () => console.log('changed!')
const watchCallback = useDebounceFn(callback, 500)

watch(
  source,
  () => {
    watchCallback()
  },
)

const clickedFn = () => {
  source.value++
}

</script>

<template>
 <div>{{source}}</div>
  <button @click="clickedFn">
    点击按钮
  </button>
</template>

如上代码引入了useDebounceFn,并用其对回调函数callback进行封装,watch执行的回调是防抖后的函数watchCallback。代码执行效果如下:

点击了3次按钮但是只触发了1次watch回调。

感觉这个版本的代码不比使用watchWithFilter复杂,但是感觉没有那种方式优雅,有同感吗?感觉那种通过参数配置的方式来写代码看起来高大上,下面就来分析一下watchWithFilter的源码。

2.源码分析

import { watch } from 'vue-demi'
import { bypassFilter, createFilterWrapper } from '../utils'
export function watchWithFilter<Immediate extends Readonly<boolean> = false>(
source: any,
 cb: any,
 options: WatchWithFilterOptions<Immediate> = {},
): WatchStopHandle {
  const {
    eventFilter = bypassFilter,
    ...watchOptions
  } = options
  
  return watch(
    source,
    createFilterWrapper(
      eventFilter,
      cb,
    ),
    watchOptions,
  )
}

watchWithFilter方法体中首先对参数options进行解构,获取eventFilter和watchOptions(传递给watch的选项)。eventFilter就是要用来对回调函数cb进行包装的函数,这里配合createFilterWrapper来使用,关于createFilterWrapper我们已经在之前的文章中分析了。eventFilter的默认值是bypassFilter,其源码如下:

export const bypassFilter: EventFilter = (invoke) => {
  return invoke()
}

代码非常简单,接收参数invoke, 并返回对invoke的调用。至此整个watchWithFilter的源码分析完了,是不是非常简单呢?

3.总结

watchWithFilter用于对watch的回调函数进行控制,本质上在于传给watch的回调函数是使用createFilterWrapper函数以及相应的过滤器函数eventFilter包装后的。本文的demo代码已经上传至github,欢迎clone代码并亲自尝试一下watchWithFilter的使用~

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿