使用
const req1 = (ctx: {id: string}) => {
return new Promise((resolve) => setTimeout(resolve, 100))
}
const [req, {isLoading, result, error, isLoaded}] = useLoadingFn(req1, {
key: ({ id }) => id
})
const req = useLoadingFn(req1, {
key: ({ id }) => id
})
定义
import type { Ref } from 'vue'
export type UseLoadingFnKey = PropertyKey
type TruthyFn = (key?: UseLoadingFnKey) => boolean
type Result<T extends AnyFn> = Ref<Awaited<ReturnType<T>> | undefined>
export type UseLoadingFnReturn<T extends AnyFn> = [
UseLoadingFnReturn<T>,
{
error: Ref<any>
isLoaded: TruthyFn
isLoading: TruthyFn
result: Result<T>
}
] &
T & {
error: Ref<any>
isLoaded: TruthyFn
isLoading: TruthyFn
result: Result<T>
}
export type UseLoadingFnOptions<T extends AnyFn> = {
key?: (...args: Parameters<T>) => UseLoadingFnKey
}
const SINGLE_KEY = '__single'
export function useLoadingFn<T extends AnyFn>(
fn: T,
options?: UseLoadingFnOptions<NoInfer<T>>,
): UseLoadingFnReturn<NoInfer<T>> {
const loadingMap = reactive(new Map<UseLoadingFnKey, boolean>())
const result: Result<T> = ref()
const error = ref<any>()
const loadedMap = reactive(new Map<UseLoadingFnKey, boolean>())
const loadingFn = (async (...args) => {
const key =
options?.key?.(...(args as Parameters<T>)) ??
(SINGLE_KEY as UseLoadingFnKey)
try {
loadingMap.set(key, true)
const ret = await fn(...args)
result.value = ret
return ret
} catch (_error: any) {
error.value = new Error(_error.message ?? _error.msg)
throw _error
} finally {
loadedMap.set(key, true)
loadingMap.set(key, false)
}
}) as UseLoadingFnReturn<T>
const isLoading = (key: UseLoadingFnKey = SINGLE_KEY) => {
if (loadingMap.has(key)) {
return loadingMap.get(key) ?? false
}
return false
}
const isLoaded = (key: UseLoadingFnKey = SINGLE_KEY) => {
if (loadedMap.has(key)) {
return loadedMap.get(key) ?? false
}
return false
}
const arr = [loadingFn, {isLoading, result, error, isLoaded}] as const
loadingFn[Symbol.iterator] = arr[Symbol.iterator].bind(arr)
loadingFn.isLoaded = isLoaded
loadingFn.isLoading = isLoading
loadingFn.result = result
loadingFn.error = error
return loadingFn
}