vueuse-useAsyncState

961 阅读1分钟

useAsyncState

Reactive async state. Will not block your setup function and will trigger changes once the promise is ready. 反应式异步状态。不会阻止您的设置功能,并且会在承诺准备就绪后触发更改

刚在用真香,又可以多些时间愉快地摸鱼,稍稍看一下其中实现如下:

// hook/useAsyncState/index.ts

// import { noop, promiseTimeout } from '@vueuse/shared'
// import type { Ref, UnwrapRef } from 'vue'
// import { ref, shallowRef } from 'vue'

function noop(): void {}

function promiseTimeout(ms: number, throwOnTimeout?: boolean, reason?: string) :Promise<void>{
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            throwOnTimeout ? reject(reason) : resolve(undefined)
        }, ms)
    })
}

export interface UseAsyncStateReturn<Data, Shallow extends boolean> {
    state: Shallow extends true ? Ref<Data> : Ref<UnwrapRef<Data>>
    isReady: Ref<boolean>
    isLoading: Ref<boolean>
    error: Ref<unknown>
    execute: (delay: number, ...args: any[]) => Promise<Data>
}

/**
 * options入参类型
 * @param delay 首次进入延时执行函数时间
 * @param immediate 是否立即执行
 * @param onError 错误回调函数
 * @param resetOnExecute 函数执行前把state重置成initialState
 * @param shallow 是否shallowRef
 */

export interface AsyncStateOptions<Shallow extends boolean> {
    delay?: number
    immediate?: boolean
    onError?: (e: unknown) => void
    resetOnExecute?: boolean
    shallow?: Shallow
}

export function useAsyncState<Data, Shallow extends boolean = true>(
    promise: Promise<Data> | ((...args: any[]) => Promise<Data>),
    initialState: Data,
    options?: AsyncStateOptions<Shallow>,
): UseAsyncStateReturn<Data, Shallow> {
    const {
        immediate = true,
        delay = 0,
        onError = noop,
        resetOnExecute = true,
        shallow = true,
    } = options ?? {}
    const state = shallow ? shallowRef(initialState) : ref(initialState)
    const isReady = ref(false)
    const isLoading = ref(false)
    const error = ref<unknown | undefined>(undefined)

    // 包装异步函数进行变更状态赋值结果
    async function execute(delay: number = 0, ...args: any[]) {
        if (resetOnExecute)
            state.value = initialState
        error.value = undefined
        isReady.value = false
        isLoading.value = true

        if (delay > 0)
            await promiseTimeout(delay)

        const _promise = typeof promise === 'function'
            ? promise(...args)
            : promise

        try {
            state.value = await _promise
            isReady.value = true
        } catch (err) {
            error.value = err
            onError(err)
        } finally {
            isLoading.value = false
        }

        return state.value as Data
    }

    // 首次是否延迟delay毫秒执行包装函数
    if (immediate)
        execute(delay)

    // 返回响应式数据
    return {
        state: state as Shallow extends true ? Ref<Data> : Ref<UnwrapRef<Data>>,
        isReady,
        isLoading,
        error,
        execute,
    }
}

demo

// useAsyncStateDemo.vue
<script setup lang="ts">
import axios from "axios";
import { useAsyncState } from "../../hook/useAsyncState/index.ts";

function request(args) {
  const id = args?.id || 1;
  return axios
    .get(`https://jsonplaceholder.typicode.com/todos/${id}`)
    .then((t) => t.data);
}

const { isLoading, state, isReady, execute } = useAsyncState(
  request,
  { a: 1 },
  {
    delay: 0,
    resetOnExecute: false,
  }
);

watch(
  state,
  (x) => {
    console.log("state=>", x);
  },
  {
    immediate: true,
  }
);

</script>

<template>
  <div>
    <p>Ready: {{ isReady.toString() }}</p>
    <p>Loading: {{ isLoading.toString() }}</p>
    <pre lang="json" class="ml-2">{{ state }}</pre>
    <button
      m-2
      px-2
      py-1
      bg-gray-500
      color-white
      border="~ base rounded"
      @click="execute(2000, { id: 3 })"
    >
      Execute
    </button>
  </div>
</template>

最后

~~useAsyncState实现过程比较简单,但是参数较多,看一下实现过程理解更深 ~~ 最后分享一下unplugin-auto-import和unplugin-vue-components这两个vite插件自动地帮我们处理依赖引入和组件引入