Vue 3 中的 useRequest 自定义 Hook 实现

Vue 3 中的 useRequest 自定义 Hook 实现

在现代的 Vue 3 应用开发中,通常会使用一些自定义 Hook 来封装常见的逻辑,如网络请求、状态管理等。在这篇文章中,我们将深入分析如何在 Vue 3 中实现一个通用的 useRequest Hook,该 Hook 用于发送网络请求,并且支持处理请求的生命周期(如请求前、成功、失败等回调)。

1. 需求分析

首先,我们需要一个功能强大的 useRequest Hook,它能够:

  • 发送网络请求,并在请求成功或失败时提供回调。
  • 支持可选的参数,像 manualonBeforeonSuccess 等。
  • 支持格式化返回的数据。
  • 在请求过程中提供 loading 状态,并处理错误。

2. useRequest 实现

接下来,我们将展示如何实现这个 useRequest 自定义 Hook。我们会使用 TypeScript 来提供类型安全,并且通过 Vue 3 中的 refnextTick 来管理状态。

代码结构

我们有三个版本的 useRequest 函数。第一个和第二个是函数重载,最后一个实现了实际的请求逻辑。

import { nextTick, ref, Ref } from 'vue';

// 定义请求的配置类型
type OptionsF<Data, Param, Format> = Partial<Options<Data, Param>> & {
  format: (data: Data) => Format;
};

type Options<Data, Param> = {
  manual: boolean;
  onBefore: (param: Param) => void;
  onSuccess: (data: Data, param: Param) => void;
  onError: (e: Error, param: Param) => void;
  onFinally: (param: Param, data?: Data, e?: Error) => void;
};

// 第一个重载:带有 format 的请求配置
export default function useRequest<Data, Param extends Array<any>, F>(
  req: (...args: Param) => Promise<Data>,
  options: OptionsF<Data, Param, F>,
): {
  data: Ref<F>;
  loading: Ref<boolean>;
  run: (...args: Param) => Promise<F>;
  error: Ref<Error>;
};

// 第二个重载:没有 format 的请求配置
export default function useRequest<Data, Param extends Array<any>>(
  req: (...args: Param) => Promise<Data>,
  options?: Partial<Options<Data, Param>>,
): {
  data: Ref<Data>;
  loading: Ref<boolean>;
  run: (...args: Param) => Promise<Data>;
  error: Ref<Error>;
};

// 第三个实现:合并两者的逻辑,支持所有类型的请求配置
export default function useRequest<Data, Param extends Array<any>, F>(
  req: (...args: Param) => Promise<Data>,
  options?: Partial<Options<Data, Param>> | OptionsF<Data, Param, F>,
): any {
  const loading = ref<boolean>(false);
  const error = ref<Error>();
  const data = ref<F>();
  
  const run: typeof req = (...param) =>
    new Promise((resolve, reject) => {
      // 请求前的回调
      options && options.onBefore && options.onBefore(param);
      loading.value = true;

      req(...param)
        .then(res => {
          // 判断是否有 format 方法,进行数据格式化
          if (Object.prototype.hasOwnProperty.call(options ?? {}, 'format')) {
            (data.value as any) = (options as any).format(res);
          } else {
            (data.value as any) = res;
          }
          nextTick(() => {
            // 请求成功的回调
            options && options.onSuccess && options.onSuccess(res, param);
            // 请求结束的回调
            options && options.onFinally && options.onFinally(param, res);
            resolve(res);
            loading.value = false;
          });
        })
        .catch(err => {
          // 请求失败的回调
          error.value = err;
          options && options.onError && options.onError(err, param);
          // 请求结束的回调
          options && options.onFinally && options.onFinally(param, undefined, err);
          reject(err);
          loading.value = false;
        });
    });

  // 如果没有 manual 标志,默认自动执行请求
  if (!(options?.manual ?? false)) {
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    //@ts-ignore
    run();
  }

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

3. 代码分析

3.1 类型定义

我们首先定义了两个类型:OptionsOptionsF

  • Options<Data, Param> 是最基本的请求配置类型,它包含以下属性:

    • manual:是否手动控制请求的发起,默认自动发起。
    • onBefore:请求发起前的回调。
    • onSuccess:请求成功时的回调。
    • onError:请求失败时的回调。
    • onFinally:请求完成后的回调。
  • OptionsF<Data, Param, Format>Options 的扩展,除了继承了 Options 中的所有属性外,还增加了一个必选的 format 属性,用于格式化请求结果数据。

3.2 函数重载

通过 TypeScript 的函数重载,我们定义了两种不同的 useRequest 函数:

  1. format 方法的请求配置,即数据需要经过格式化后再返回。
  2. 没有 format 方法的请求配置,即直接返回请求结果。

3.3 请求执行逻辑

useRequest 实际上封装了一个网络请求逻辑。在执行请求时,它会做以下几步:

  • 请求前回调 (onBefore):如果配置中有 onBefore 回调,则在请求开始前调用它。
  • 发送请求:调用传入的请求函数(如 axios.get 或其他自定义请求函数)。
  • 请求成功:如果请求成功,调用 onSuccess 回调,并通过 nextTick 触发视图更新。结果数据会通过 format 函数进行处理(如果有的话)。
  • 请求失败:如果请求失败,调用 onError 回调,并记录错误。
  • 请求完成:无论成功或失败,onFinally 回调都会被触发,用于清理资源或记录日志等操作。

3.4 自动发起请求

如果配置中没有显式设置 manual: true,请求会在 useRequest 初始化时自动发起。

if (!(options?.manual ?? false)) {
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  //@ts-ignore
  run();
}

3.5 运行状态和错误处理

  • data:存储请求返回的数据。
  • loading:表示请求是否正在进行,适合用来显示加载动画。
  • error:存储请求过程中遇到的错误。

4. 使用示例

4.1 基本用法

假设我们有一个 fetchUserData 的 API 请求,我们可以通过 useRequest 来发起请求。

import { useRequest } from './useRequest';

const { data, loading, error, run } = useRequest(fetchUserData, {
  manual: false,  // 自动发起请求
  onSuccess: (data) => {
    console.log('请求成功', data);
  },
  onError: (err) => {
    console.error('请求失败', err);
  },
});

4.2 自定义数据格式化

如果我们需要格式化返回的数据,比如将时间戳转换为日期格式,可以这样做:

const { data, loading, error, run } = useRequest(fetchUserData, {
  manual: false,
  format: (data) => {
    return {
      ...data,
      formattedDate: new Date(data.timestamp).toLocaleString(),
    };
  },
  onSuccess: (data) => {
    console.log('格式化后的数据', data);
  },
});

5. 总结

本文详细分析了如何在 Vue 3 中实现一个通用的 useRequest 自定义 Hook,涵盖了请求的生命周期管理、错误处理、数据格式化等功能。通过合理的类型定义和函数重载,我们确保了这个 Hook 既灵活又安全,可以适应多种请求场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值