until封裝watch常用邏輯簡化代碼寫法
引言
在之前的系列文章中我們介紹了vueuse對watch封裝的一系列方法,以便我們可以更高效的開發(fā)。有對回調(diào)進行控制的watchWithFilter,有適用于當watch的值為真值時觸發(fā)回調(diào)的whenever,還有只觸發(fā)一次的watchOnce和最多觸發(fā)一定次數(shù)的watchAtMost。但是我們最常用的場景可能是被觀察的變量在滿足某個具體條件時則觸發(fā)回調(diào),今天要學習的until就是直到滿足某種條件時則觸發(fā)一次回調(diào)函數(shù)。讓我們通過示例代碼和源碼來研究一下吧~
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>
如上代碼所示,規(guī)定了當source的值為4的時候觸發(fā)執(zhí)行watch回調(diào)函數(shù)。這里使用到了invoke方法,我們之前接觸過,源碼如下
export function invoke<T>(fn: () => T): T {
return fn()
}
給定參數(shù)fn為一個函數(shù),invoke返回函數(shù)的執(zhí)行結果。代碼運行效果如下圖所示:

當點擊次數(shù)達到4次時,打印了相應的信息。
2.源碼
until代碼較多,先看兩張預覽圖,了解一下其大概實現(xiàn):


通過以上兩張圖片我們看到until內(nèi)部定義了很多的用于判斷條件是否滿足的方法,最后返回的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構造函數(shù)的參數(shù)函數(shù)中調(diào)用watch API來監(jiān)聽數(shù)據(jù)源r 。當數(shù)據(jù)源r的新值代入到條件condition中,使得condition為true時則調(diào)用stop停止監(jiān)聽數(shù)據(jù)源,并將promise狀態(tài)變?yōu)槌晒Α?/p>
promise放入promises數(shù)組中,如果用戶傳了timeout選項則promises放入調(diào)用promiseTimeout返回的promise實例。最后返回的是Promise.race的結果??匆幌聀romiseTimeout的代碼:
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變?yōu)槭顟B(tài),否則經(jīng)過ms毫秒后調(diào)用resolve,使promise變?yōu)槌晒顟B(tài)。
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回調(diào)函數(shù)不同。這里對數(shù)據(jù)源r和toBe的參數(shù)value進行監(jiān)聽,當r的值和value的值相同時,使promise狀態(tài)為成功。注意這里的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構造函數(shù)后判斷參數(shù)v是否為真值。
toBeNaN判斷是否為NAN, 使用的是Number的isNaN作為判斷條件,注意toBeNaN的實現(xiàn)不能使用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)
}
判斷數(shù)據(jù)源v中是否有value,Array.from把v轉換為數(shù)組,然后使用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用于判斷是否改變,通過調(diào)用changedTimes和固定第一參數(shù)n為1實現(xiàn)的。changedTimes的第一個參數(shù)為監(jiān)聽的數(shù)據(jù)源改變的次數(shù),也是通過調(diào)用toMatch實現(xiàn)的,傳給toMatch的條件是一個函數(shù),此函數(shù)會在數(shù)據(jù)源改變時調(diào)用。每調(diào)用一次外層作用域定義的count就會累加一次 ,注意外層作用域count變量聲明為-1, 因為時立即監(jiān)聽的。
至此,until源碼內(nèi)定義的函數(shù)全部分析完畢,下圖總結了這些函數(shù)之前的調(diào)用關系:

源碼中最后的返回值也值得我們說一說。
2.6 until返回值——instance
until的返回值分為兩種情況:當監(jiān)聽的源數(shù)據(jù)是數(shù)組時和不是數(shù)組時,代碼如下圖所示:
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
}
我們看到數(shù)據(jù)源時數(shù)組時返回的方法中沒有toBeTruthy,toBeNull,toBeNaN,toBeUndefined這些用于判斷基本類型值的方法。另外需要注意的是返回的instance里面有一個get not(){// ...}這是使用getters, 用于獲取特定的屬性(這里是not)。在getter里面對isNot取反,isNot返回值為this也就是instance本身,所以讀取完not屬性后可以鏈式調(diào)用其他方法,如下所示:
await until(ref).not.toBeNull() await until(ref).not.toBeTruthy()
3.總結
until方法用于對數(shù)據(jù)監(jiān)聽,返回具有多個條件判斷函數(shù)的對象,使用者可以將條件做為這些函數(shù)的參數(shù),當監(jiān)聽的數(shù)據(jù)滿足條件則停止監(jiān)聽,其本質(zhì)是對watch的回調(diào)進行封裝,并結合promise.race的一個異步方法。本文的demo代碼已經(jīng)上傳至github, 歡迎您clone并親自體驗until的使用。
以上就是until封裝watch常用邏輯簡化代碼寫法的詳細內(nèi)容,更多關于until封裝watch邏輯的資料請關注腳本之家其它相關文章!
相關文章
vue項目中的支付功能實現(xiàn)(微信支付和支付寶支付)
本文主要介紹了vue項目中的支付功能實現(xiàn)(微信支付和支付寶支付),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01
vue.js 實現(xiàn)點擊按鈕動態(tài)添加li的方法
今天小編就為大家分享一篇vue.js 實現(xiàn)點擊按鈕動態(tài)添加li的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09

