Vue手动实现useRequest

功能介绍

  useRequest 是个网络请求Hooks,可以解决Vue3开发过程中,常见的网络请求场景,核心功能如下:

  • 👨‍🍳 状态获取
  • 💦 节流
  • 🫨 防抖
  • ☎️ 依赖刷新
// 基本用法
const { data, loading, run } = useRequest(asyncFn);

相关参数 

参数名 类型 默认值 描述
manual
boolean
true 设置该参数为true,useRequest不会默认执行,需要通过run来触发执行
throttleWait
number undefined 设置该参数(毫秒),若频繁触发run方法,则会以节流策略进行请求
debounceWait
number undefined 设置该参数(毫秒),若频繁触发run方法,则会以防抖策略进行请求
refreshDeps
function undefined 设置该参数,在依赖变化时, 会自动调用run方法,实现刷新的效果
defaultValue
any undefined data的初始化默认值
clearPrevValue
boolean false 设置该参数为true,执行run方法时,会将data的值恢复为默认值

Typescript 类型定义

import { watch } from "vue";

import type { Ref } from "vue";

/**
 * @name TypeUseRequestAsyncFn 第一个参数 传递的异步函数
 */
export type TypeUseRequestAsyncFn<T = any> = (...args: any[]) => Promise<T>;

/**
 * @name TypeUseRequestParameters 第二个参数 相关配置
 */
export type TypeUseRequestParameters = Partial<{
  /**
   * @param manual 初始化请求一次
   * @default false
   */
  manual: boolean;
  /**
   * @param throttleWait 节流执行时间(毫秒)
   * @description 传递值后生效
   */
  throttleWait: number;
  /**
   * @param debounceWait 防抖执行时间(毫秒)
   * @description 传递值后生效
   */
  debounceWait: number;
  /**
   * @param clearPrevValue 清空上一轮值为默认值
   * @default undefined
   * @description 若执行run方法,则立即清空data的值为默认值(defaultValue)
   */
  clearPrevValue: boolean;
  /**
   * @param defaultValue 默认值
   * @default undefined
   * @description 若执行run方法,则立即清空data的值为默认值(defaultValue)
   */
  defaultValue: any;
  /**
   * @name refreshDeps 监听参数,调用run方法
   * @description 跟Vue3 watch API用法完全一致
   */
  refreshDeps: typeof watch;
}>;

type TypeUseRequest<
  T extends TypeUseRequestAsyncFn = TypeUseRequestAsyncFn,
  R = Awaited<ReturnType<T>>,
> = (fn: R) => {
  data: Ref<R>;
  run: () => Promise<R>;
  loading: Ref<boolean>;
};

async function getList() {
  return [1, true, "2"];
}

// 调用useRequest方法,得到的结果 完整的Typescript结果类型
type Returns = TypeUseRequest<typeof getList>;

基本功能

  🤖  useRequest 基本功能,包括:

  • 💁‍♂️ manual    初始化是否执行run方法      
  • 💁‍♂️ defaultValue   默认参数
  • 💁‍♂️ clearPrevValue  执行新的请求执行前,将data的值恢复为默认值
  • 🙆‍♂️ return  dataloadingrun 参数,供业务组件使用
import { onMounted, readonly, ref } from "vue";

import type { TypeUseRequestAsyncFn, TypeUseRequestParameters } from "./interface";

/**
 * @name useRequest 🚀 网络请求Hooks
 */
function useRequest<T extends TypeUseRequestAsyncFn, R = Awaited<ReturnType<T>>>(
  fn: T,
  params?: TypeUseRequestParameters
) {
  const defaultValue = readonly(params?.defaultValue);

  const loading = ref(false);
  const data = ref<R>(defaultValue);

  async function run() {
    try {
      // 是否清空上一轮值
      if (params?.clearPrevValue) {
        data.value = defaultValue;
      }
      loading.value = true; // 更新loading状态
      const res = await fn();
      data.value = res;
      return res as R
    } catch (error) {
      // 错误埋点
      console.log("@-error", error);
    } finally {
      loading.value = false;
    }
  }

  onMounted(() => {
    // 初始化是否执行
    params?.manual || run();
  });

  return {
    run,
    data,
    loading,
  };
};

防抖与节流 

  防抖节流作为请求场景中最常见的需求,实现起来也非常简单,不需要引入任何三方库就可以快速实现。

  🧑‍💻 以下是手动实现 throttle debounce 的方法,直接上代码:

/**
 * @name throttle 节流
 */
function throttle<R>(fn: Function, delay = 0) {
  let lastTime = 0
  return function (...args: unknown[]) {
    const now = Date.now()
    if (now - lastTime >= delay) {
      let data = fn(...args)
      lastTime = now
      return data as R
    }
  }
}

/**
 * @name debounce 防抖
 */
function debounce<R>(fn: Function, delay = 0) {
  let timerId: number
  let immediately = true
  return function (...args: unknown[]) {
    clearTimeout(timerId)
    return new Promise<R>((resolve) => {
      if (immediately) {
        immediately = false
        resolve(fn(...args))
      } else {
        timerId = setTimeout(() => {
          immediately = true
          resolve(fn(...args))
        }, delay)
      }
    })
  }
}

👩‍💻 通过 throttleWaitdebounceWait参数匹配,将throttledebounce 方法运用在 useRequst 返回的run方法中,实现防抖节流

function useRequest<T extends TypeUseRequestAsyncFn, R = Awaited<ReturnType<T>>>(
  fn: T,
  params?: TypeUseRequestParameters
) {
  // ...
  return {
    run: params?.throttleWait
      ? throttle<R>(run, params.throttleWait) // 节流
      : params?.debounceWait
      ? debounce<R>(run, params.debounceWait) // 防抖
      : run, // 请求方法
    data,
    loading,
  };
};

依赖刷新 

 👀 Vue3 watch API(官方文档),可通过监听refreactive对象变化,从而调用回调函数,实现依赖刷新功能。useRequest可以借助此特性,实现该场景功能需求。

import { watch } from 'vue';

function useRequest<T extends TypeUseRequestAsyncFn, R = Awaited<ReturnType<T>>>(
  fn: T,
  params?: TypeUseRequestParameters
) {
  // ...
  const returns = {
    run: params?.throttleWait
      ? throttle<R>(run, params.throttleWait) // 节流
      : params?.debounceWait
      ? debounce<R>(run, params.debounceWait) // 防抖
      : run, // 请求方法
    data,
    loading,
  };

  // 实现 “依赖刷新”
  watch(params?.refreshDeps || Function, () => returns.run(), { deep: true });

  return returns;
};

这样,一个最基本的 useRequest Hooks就实现了,有其他的需求可以继续添加。

👉 源码地址