until封装watch常用逻辑简化代码写法

目录
  • 引言
  • 1.示例
  • 2.源码
    • 2.1 toMatch
    • 2.2 toBe
    • 2.3 toBeTruthy、toBeNull、toBeUndefined、toBeNaN
    • 2.4 toContains
    • 2.5 changed和changedTimes
    • 2.6 until返回值——instance
  • 3.总结

引言

在之前的系列文章中我们介绍了vueuse对watch封装的一系列方法,以便我们可以更高效的开发。有对回调进行控制的watchWithFilter,有适用于当watch的值为真值时触发回调的whenever,还有只触发一次的watchOnce和最多触发一定次数的watchAtMost。但是我们最常用的场景可能是被观察的变量在满足某个具体条件时则触发回调,今天要学习的until就是直到满足某种条件时则触发一次回调函数。让我们通过示例代码和源码来研究一下吧~

1.示例

结合文档的介绍,笔者写了如下的demo代码:

<script setup lang="ts">
import { until , invoke } from '@vueuse/core'
import {ref} from 'vue'
const source = ref(0)
invoke(async () => {
  await until(source).toBe(4)
  console.log('满足条件了')
})
const clickedFn = () => {
  source.value ++
}
</script>
<template>
 <div>{{source}}</div>
  <button @click="clickedFn">
    点击按钮
  </button>
</template>

如上代码所示,规定了当source的值为4的时候触发执行watch回调函数。这里使用到了invoke方法,我们之前接触过,源码如下

export function invoke<T>(fn: () => T): T {
  return fn()
}

给定参数fn为一个函数,invoke返回函数的执行结果。代码运行效果如下图所示:

当点击次数达到4次时,打印了相应的信息。

2.源码

until代码较多,先看两张预览图,了解一下其大概实现:

通过以上两张图片我们看到until内部定义了很多的用于判断条件是否满足的方法,最后返回的instance也是包含这些方法的对象。下面我们对这些方法逐个分析。

2.1 toMatch

function toMatch(
    condition: (v: any) => boolean,
    { flush = 'sync', deep = false, timeout, throwOnTimeout }: UntilToMatchOptions = {},
  ): Promise<T> {
    let stop: Function | null = null
    const watcher = new Promise<T>((resolve) => {
      stop = watch(
        r,
        (v) => {
          if (condition(v) !== isNot) {
            stop?.()
            resolve(v)
          }
        },
        {
          flush,
          deep,
          immediate: true,
        },
      )
    })
    const promises = [watcher]
    if (timeout != null) {
      promises.push(
        promiseTimeout(timeout, throwOnTimeout)
          .then(() => unref(r))
          .finally(() => stop?.()),
      )
    }
    return Promise.race(promises)
  }

在promise构造函数的参数函数中调用watch API来监听数据源r 。当数据源r的新值代入到条件condition中,使得condition为true时则调用stop停止监听数据源,并将promise状态变为成功。

promise放入promises数组中,如果用户传了timeout选项则promises放入调用promiseTimeout返回的promise实例。最后返回的是Promise.race的结果。看一下promiseTimeout的代码:

export function promiseTimeout(
  ms: number,
  throwOnTimeout = false,
  reason = 'Timeout',
): Promise<void> {
  return new Promise((resolve, reject) => {
    if (throwOnTimeout)
      setTimeout(() => reject(reason), ms)
    else
      setTimeout(resolve, ms)
  })
}

promiseTimeout返回了一个promise, 如果throwOnTimeout为true则过ms毫秒之后则将promise变为失败状态,否则经过ms毫秒后调用resolve,使promise变为成功状态。

2.2 toBe

function toBe<P>(value: MaybeRef<P | T>, options?: UntilToMatchOptions) {
    if (!isRef(value))
      return toMatch(v => v === value, options)
    const { flush = 'sync', deep = false, timeout, throwOnTimeout } = options ?? {}
    let stop: Function | null = null
    const watcher = new Promise<T>((resolve) => {
      stop = watch(
        [r, value],
        ([v1, v2]) => {
          if (isNot !== (v1 === v2)) {
            stop?.()
            resolve(v1)
          }
        },
        {
          flush,
          deep,
          immediate: true,
        },
      )
    })
     // 和toMatch相同部分省略
  }

toBe方法体大部分和toMatch相同,只是watch回调函数不同。这里对数据源r和toBe的参数value进行监听,当r的值和value的值相同时,使promise状态为成功。注意这里的watch使用的是侦听多个源的情况。

2.3 toBeTruthy、toBeNull、toBeUndefined、toBeNaN

function toBeTruthy(options?: UntilToMatchOptions) {
  return toMatch(v => Boolean(v), options)
}
function toBeNull(options?: UntilToMatchOptions) {
  return toBe<null>(null, options)
}
function toBeUndefined(options?: UntilToMatchOptions) {
  return toBe<undefined>(undefined, options)
}
function toBeNaN(options?: UntilToMatchOptions) {
  return toMatch(Number.isNaN, options)
}

toBeTruthy和toBeNaN是对toMatch的封装,toBeNull和toBeUndefined是对toBe的封装。toBeTruthy判断是否为真值,方法是使用Boolean构造函数后判断参数v是否为真值。

toBeNaN判断是否为NAN, 使用的是Number的isNaN作为判断条件,注意toBeNaN的实现不能使用toBe, 因为tobe在做比较的时候使用的是 ‘===’这对于NaN是不成立的:

toBeNull用于判断是否为null,toBeUndefined用于判断是否为undefined。

2.4 toContains

function toContains(
value: any,
 options?: UntilToMatchOptions,
) {
  return toMatch((v) => {
    const array = Array.from(v as any)
    return array.includes(value) || array.includes(unref(value))
  }, options)
}

判断数据源v中是否有value,Array.from把v转换为数组,然后使用includes方法判断array中是否包含value。

2.5 changed和changedTimes

function changed(options?: UntilToMatchOptions) {
  return changedTimes(1, options)
}
function changedTimes(n = 1, options?: UntilToMatchOptions) {
  let count = -1 // skip the immediate check
  return toMatch(() => {
    count += 1
    return count >= n
  }, options)
}

changed用于判断是否改变,通过调用changedTimes和固定第一参数n为1实现的。changedTimes的第一个参数为监听的数据源改变的次数,也是通过调用toMatch实现的,传给toMatch的条件是一个函数,此函数会在数据源改变时调用。每调用一次外层作用域定义的count就会累加一次 ,注意外层作用域count变量声明为-1, 因为时立即监听的。

至此,until源码内定义的函数全部分析完毕,下图总结了这些函数之前的调用关系:

源码中最后的返回值也值得我们说一说。

2.6 until返回值——instance

until的返回值分为两种情况:当监听的源数据是数组时和不是数组时,代码如下图所示:

if (Array.isArray(unref(r))) {
  const instance: UntilArrayInstance<T> = {
    toMatch,
    toContains,
    changed,
    changedTimes,
    get not() {
      isNot = !isNot
      return this
    },
  }
  return instance
}
else {
  const instance: UntilValueInstance<T, boolean> = {
    toMatch,
    toBe,
    toBeTruthy: toBeTruthy as any,
    toBeNull: toBeNull as any,
    toBeNaN,
    toBeUndefined: toBeUndefined as any,
    changed,
    changedTimes,
    get not() {
      isNot = !isNot
      return this
    },
  }
  return instance
}

我们看到数据源时数组时返回的方法中没有toBeTruthy,toBeNull,toBeNaN,toBeUndefined这些用于判断基本类型值的方法。另外需要注意的是返回的instance里面有一个get not(){// ...}这是使用getters, 用于获取特定的属性(这里是not)。在getter里面对isNot取反,isNot返回值为this也就是instance本身,所以读取完not属性后可以链式调用其他方法,如下所示:

await until(ref).not.toBeNull()
await until(ref).not.toBeTruthy()

3.总结

until方法用于对数据监听,返回具有多个条件判断函数的对象,使用者可以将条件做为这些函数的参数,当监听的数据满足条件则停止监听,其本质是对watch的回调进行封装,并结合promise.race的一个异步方法。本文的demo代码已经上传至github, 欢迎您clone并亲自体验until的使用。

以上就是until封装watch常用逻辑简化代码写法的详细内容,更多关于until封装watch逻辑的资料请关注我们其它相关文章!

(0)

相关推荐

  • Vue中的watch是什么以及watch和computed的区别

    目录 一.watch是什么? 二.应用基本用法 三.Watch和computed的区别 computed和watch的综合运用实例 需求: 实现代码(helloworld.vue实现代码) 一.watch是什么? 监测 Vue 实例变化的一个表达式或方法.回调函数得到的参数为新值和旧值,用一个函数取代. 简洁的说:watch的作用可以监控一个值的变换,并调用因为变化需要执行的方法.可以通过watch动态改变关联的状态. 二.应用基本用法 当firstName值变化时,watch监听到并且执行wa

  • Vue3 中 watch 与 watchEffect 区别及用法小结

    目录 响应式依赖收集 Watch WatchEffect 什么时候用什么? 大部分时候用 watch 显式的指定依赖以避免不必要的重复触发,也避免在后续代码修改或重构时不小心引入新的依赖.watchEffect 适用于一些逻辑相对简单,依赖源和逻辑强相关的场景. 你可以认为他们是同一个功能的两种不同形态,底层的实现是一样的. watch- 显式指定依赖源,依赖源更新时执行回调函数 watchEffect - 自动收集依赖源,依赖源更新时重新执行自身 响应式依赖收集 首先先需要了解一下 vue 3

  • vue3中的watch和watchEffect实例详解

    目录 首先总结一下两者的区别: 下面是根据上面的第三点做的一些小实验: 总结 闲来无事,比较了一下 vue3 中的 watch 和 watchEffect,总觉得官方文档没咋说清楚,今天就小小实践了一下. 首先总结一下两者的区别: 1.watch 是惰性执行,而 watchEffect 不是,不考虑 watch 的第三个参数配置的情况,watch 在组件第一次执行的时候是不会执行的,只有在之后依赖项变化的时候再执行,而 watchEffect 是在程序执行到此处的时候就立即执行,而后再响应其依赖

  • vue前端测试开发watch监听data的数据变化

    目录 watch监听data的数据变化 新问题 解决 1. 先把姓名的值,也加到options里 2. 在监听里增加for循环和判断 watch监听data的数据变化 上一篇里提到了用elementUI的select实现了个远程搜索的功能,最终效果是这样的. 但是继续开发的时候,又遇到了一个新的问题,跟上面的功能有关. 先看下远程搜索的操作,与data里的数据关系. 当输入“张”进行搜索,看到的下拉列表里展示的结果都是存放在data的options: []. 当我选择了一个,比如“张三”,因为s

  • Vue使用watch监听数组或对象

    1.普通的watch data() { return { frontPoints: 0 } }, watch: { frontPoints(newValue, oldValue) { console.log(newValue) } } 2.数组的watch data() { return { winChips: new Array(11).fill(0) } }, watch: { winChips: { handler(newValue, oldValue) { for (let i = 0;

  • VUE3中watch监听使用的不同情况总结

    目录 watch介绍 watch监听的不同情况 1 监听单个refimpl数据 2 监听多个refimpl数据 3 监听proxy数据 4 监听proxy数据的某个属性 5 监听proxy数据的某些属性 总结 watch介绍 vue中watch用来监听数据的响应式变化.获取数据变化前后的值 watch的完整入参 watch(监听的数据,副作用函数,配置对象) watch(data, (newData, oldData) => {}, {immediate: true, deep: true})

  • until封装watch常用逻辑简化代码写法

    目录 引言 1.示例 2.源码 2.1 toMatch 2.2 toBe 2.3 toBeTruthy.toBeNull.toBeUndefined.toBeNaN 2.4 toContains 2.5 changed和changedTimes 2.6 until返回值——instance 3.总结 引言 在之前的系列文章中我们介绍了vueuse对watch封装的一系列方法,以便我们可以更高效的开发.有对回调进行控制的watchWithFilter,有适用于当watch的值为真值时触发回调的wh

  • javascript下with 的简化代码写法

    with (object) statements 参数 object 新的默认对象. statements 一个或多个语句,object 是该语句的默认对象. 说明 with 语句通常用来缩短特定情形下必须写的代码量.在下面的例子中,请注意 Math 的重复使用: x = Math.cos(3 * Math.PI) + Math.sin(Math.LN10) y = Math.tan(14 * Math.E) 当使用 with 语句时,代码变得更短且更易读: 复制代码 代码如下: with (M

  • 微信js-sdk分享功能接口常用逻辑封装示例

    本文实例讲述了微信js-sdk分享功能接口常用逻辑封装.分享给大家供大家参考,具体如下: 微信js-sdk 1.0,分享说明: 1.目前支持的分享接口5个,其中分享腾讯微博基本可以忽略 2.接口只是定义分享时的数据,没有提供触发分享功能 一.在ready事件使用使用示例 //分享对象使用 var shareData={ title:'分享标题', desc:'分享描述', link:'http://www.gongjuji.net', imgUrl:'http://www.gongjuji.ne

  • Java8如何使用Lambda表达式简化代码详解

    系统环境: Java JDK 版本:1.8 参考地址: Java 8 Lambda 表达式 Jdk 8 新特性 04 方法引用与构造器引用 Java 8 新特性:Lambda 表达式之方法引用 一.Lambda 表达式简介 1.什么是 Lambda 表达式 Lambda 表达式是在 JDK 8 中引入的一个新特性,可用于取代大部分的匿名内部类.使用 Lambda 表达式可以完成用少量的代码实现复杂的功能,极大的简化代码代码量和代码结构.同时,JDK 中也增加了大量的内置函数式接口供我们使用,使得

  • Bootstrap Multiselect 常用组件实现代码

    实际的项目网站中或多或少的或用到多选框,我选用的一款是 Bootstrap Multiselect. 官方文档:http://www.kuitao8.com/demo/20140224/1/bootstrap-multiselect-master/index.html 如果你英文好一点,里面有详细的介绍,多选框的设置,多选框获取值/文本,选项分组,各种各样丰富的表现方式和获取. 结合实际项目,加深技术理解,同时也方便自己后续项目中的使用. 多选框和单选框相同,实际项目中前端不可能将里面的选项很直

  • CI框架封装的常用图像处理方法(缩略图,水印,旋转,上传等)

    本文实例讲述了CI框架封装的常用图像处理方法.分享给大家供大家参考,具体如下: 其实微信手机端上图时,列表图最好是缩略图,节省流量,这不,又被移动坑了一把,话费签一分就停机,流量欠到90块才停机,我也是醉了... 不说废话了,下面是用CI 的内置处理图像的库写的,小弟不才,遗漏之处敬请指出,谢谢. /** * 生成缩略图 * @param $path 原图的本地路径 * @return null 创建一个 原图_thumb.扩展名 的文件 * */ public function dealthu

  • jQuery封装的屏幕居中提示信息代码

    本文实例讲述了jQuery封装的屏幕居中提示信息代码.分享给大家供大家参考,具体如下: function showLoad(tipInfo, type, autohide) { var pic = ""; switch (type) { case 0: // loading pic = "./Images/loading.gif"; break; case 1: // ok pic = "./Images/right.png"; break; c

  • Python访问MySQL封装的常用类实例

    本文实例讲述了Python访问MySQL封装的常用类.分享给大家供大家参考.具体如下: python访问mysql比较简单,下面整理的就是一个很简单的Python访问MySQL数据库类. 自己平时也就用到两个mysql函数:查询和更新,下面是自己常用的函数的封装,大家拷贝过去直接可以使用. 文件名:DBUtil.py 复制代码 代码如下: # -*- encoding:utf8 -*- ''' @author: crazyant.net @version: 2013-10-22   封装的mys

  • Java及Android中常用链式调用写法简单示例

    本文实例讲述了Java及Android中常用链式调用写法.分享给大家供大家参考,具体如下: 最近发现,目前大火的许多开源框架中,大多都使用了一种"(方法).(方法).(方法)"的形式进行调用,最典型的就是RxJava.android中AlertDialog控件的源码也是这种形式的.查阅可知,大家把它叫做链式调用."行动是检验程序的唯一标准"0.0!查了.说了那么多,还是得自己写个例子并运行出预期的效果. /** * * 链式调用 * * @author k.k *

  • Kotlin中单利常用的五种写法

    前言 单利模式是写代码过程中不可避免用到的,下面我总结一下单利常用的五种写法,话不多说了,来一起看看详细的介绍吧 加载类时创建单利 Java实现 public class Config{ private static Config INSTANCE=new Config(); private Config(){ //构造函数 } public static Config getInstance(){ return INSTANCE; } } Kotlin实现 object Config{} 上面

随机推荐