Vue 3 中的 useRequest
自定义 Hook 实现
在现代的 Vue 3 应用开发中,通常会使用一些自定义 Hook 来封装常见的逻辑,如网络请求、状态管理等。在这篇文章中,我们将深入分析如何在 Vue 3 中实现一个通用的 useRequest
Hook,该 Hook 用于发送网络请求,并且支持处理请求的生命周期(如请求前、成功、失败等回调)。
1. 需求分析
首先,我们需要一个功能强大的 useRequest
Hook,它能够:
- 发送网络请求,并在请求成功或失败时提供回调。
- 支持可选的参数,像
manual
、onBefore
、onSuccess
等。 - 支持格式化返回的数据。
- 在请求过程中提供
loading
状态,并处理错误。
2. useRequest
实现
接下来,我们将展示如何实现这个 useRequest
自定义 Hook。我们会使用 TypeScript 来提供类型安全,并且通过 Vue 3 中的 ref
和 nextTick
来管理状态。
代码结构
我们有三个版本的 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 类型定义
我们首先定义了两个类型:Options
和 OptionsF
。
-
Options<Data, Param>
是最基本的请求配置类型,它包含以下属性:manual
:是否手动控制请求的发起,默认自动发起。onBefore
:请求发起前的回调。onSuccess
:请求成功时的回调。onError
:请求失败时的回调。onFinally
:请求完成后的回调。
-
OptionsF<Data, Param, Format>
是Options
的扩展,除了继承了Options
中的所有属性外,还增加了一个必选的format
属性,用于格式化请求结果数据。
3.2 函数重载
通过 TypeScript 的函数重载,我们定义了两种不同的 useRequest
函数:
- 带
format
方法的请求配置,即数据需要经过格式化后再返回。 - 没有
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 既灵活又安全,可以适应多种请求场景。