在以前的请求中,我们需要定义loading,error,data等字段
来接收请求回来的数据,然后通过自己对data的处理获取对应的格式。我尤其是不喜欢loading,和error的定义
,因为它是一个不影响我逻辑的代码,但是占用了声明和赋值两个地方,那么有没有办法解决这些问题呢?随着Vue3的兴起,我们可以利用新特性来一波更新,先来看看下面的使用文档,你是否中意!
【Git 项目地址 】
HTTP 请求
|-- http // 请求相关内容
|-- request.ts // 请求初始化配置
|-- index.ts // 导出GET,POST请求方式
使用文档
生成请求 hook
type QueryType = {
pageSize: number;
page: number;
keyword: string;
userInfo: {
name: string;
};
};
type ResponseType = {
list: Array<{ name: string; age: number; icon: string }>;
};
type OtherQueryType = {
pageSize: Ref<number>;
page: Ref<number>;
keyword: Ref<string>;
userInfo: {
name: string;
};
};
export const useMenuList = createHttpGetFactory<OtherQueryType, ResponseType>(
"/menu/list"
);
export const useUpdateUserInfo = createHttpPostFactory<QueryType, ResponseType>(
"/user/register"
);
调用请求 hook
分散型参数
import { useMenuList } from "@/api/common";
import { ref } from "vue";
const page = ref(1);
const pageSize = ref(10);
const keyword = ref("1");
const userInfo = reactive({
name: "test",
});
const [{ loading, data, error }, { abortController, execute }] = useMenuList({
enabled: (newVal) => newVal.page.value === 1,
watchKeys: ["page", "pageSize", "userInfo"],
query: {
page: page,
pageSize: pageSize,
keyword: keyword,
userInfo: userInfo,
},
onSuccess: (data) => {
console.log(data);
return data.list[0];
},
});
const cancel = () => {
abortController.cancel();
};
const handleSizeChange = (val: number) => {
params.pageSize = val;
params.page = 1;
execute();
};
集中型参数
import { useUpdateUserInfo } from "@/api/common";
import { reactive } from "vue";
// 参数二模式
const params = reactive({
page: page,
pageSize: 10,
keyword: "1",
userInfo: {
name: "test",
},
});
const [{ loading, error }, { execute }] = useUpdateUserInfo({
query: params,
successTips: "注册成功",
onSuccess: (data) => {
console.log(data);
return "info";
},
});
const submit = async () => {
const res = await execute(
{ keyword: "我的新参数" },
{ message: "一下子就成功了" }
);
if (res) {
let [data, localData] = res;
console.log("哈哈哈", data, localData);
}
};
导出数据介绍
GET 请求
-
数组第一个元素:{ loading, data, error }
- loading:是否 loading 状态,具有响应式
- data:数据,具有响应式。类型默认由 ResponseType 决定,但是如果提供 onSuccess 函数,那么类型就会由 onSuccess 返回值类型决定
- error: 请求是否报错,具有响应式
-
数组第二个元素: { abortController, execute }
- execute:手动请求方法,可以传参进去进行原始参数覆盖,支持 async – await 获取返回值,返回值为数组,第一个元素为 onSuccess 处理后的数据,第二个元素为未经过 onSuccess 处理的原始数据
const submit = async () => { const res = await execute({ keyword: "新数据" }); if (res) { let [data, localData] = res; console.log("哈哈哈", data, localData); } };
- abortController:取消请求的对象,调用 abortController.cancel()即可取消当前请求
POST 请求
导出内容基本与 GET 请求相同,有以下几个不同点
- 不提供 data 参数,如果需要对 data 进行处理可以通过 onSuccess 回调处理
- 因为 GET 请求不提供弹窗提示功能所以只有一个参数,但是 POST 提供提示功能,所以 execute 方法提供第二个参数,可以覆盖默认提示
{message?: string,title?: string}
入参介绍
- createHttpGetFactory 和 createHttpGetFactory 入参
/**
- T: 入参类型限制
- D: 原始data类型
- arg1: get或者post请求参数
- arg2: 配置请求方式
- arg3: get或者post请求的配置,请求头等配置
- useGetRequest:生成的请求hook
*/
const useGetRequest = createHttpGetFactory<T, D>(arg1, arg2, arg3);
const usePostRequest = createHttpPostFactory<T, D>(arg1, arg2, arg3);
useGetRequest 入参
const [{data}] = useGetRequest({
enabled: bool | ((newVal, oldVal) => bool),
query: T,
watchKeys: Array<string>,
onSuccess: (data: D) => {
return data.list[0];
},
onError: (error) => void,
onFinally: (stopLoading: () => void) => void
});
- enabled: 是否请求:true 请求,false 不请求,可以传入 bool 类型,也可以传入一个函数,参数是新值和老值【这里的新老值就是 watch 的新老值,因为对象的特殊性,可能新值和老值一样】
- query: 请求参数,如果希望能够监听到变化,需要放入响应式数据,Ref 在内部会自动取值使用
- watchKeys: 监听 query 的 key 值数组,放在这里面的 key 都会受到 watch 监听,一旦有变化就会请求数据,但是还是会受到 enabled 限制
- onSuccess:请求成功回调,如果不提供 data 返回值类型为创建的 D 类型,如果提供该回调, data 类型变为该函数的返回值类型
- onError: 错误时调用的函数
- onFinally:请求结束时调用的函数,带有一个函数【
stopLoading
】,如果提供了该函数,则需要手动调用stopLoading
函数修改loading
状态
usePostRequest 入参
const [{ loading, error }, { execute, abortController }] = useUpdateUserInfo({
query: params,
successTips: "注册成功",
onSuccess: (data: D) => {
return "info";
},
onError: (error) => void,
onFinally: (stopLoading: () => void) => void
});
大部分与 GET 类似,一下列出特殊点
- successTips: 提示信息,不提供不弹出成功弹窗
- enabled: 不提供该参数,所有请求执行都通过 execute 实现
- watchKeys:不提供该参数
- execute: 该方法提供第二个参数,可覆盖 successTips 的提示,具体可见返回值部分 execute 介绍
如果你对上面的请求方式感兴趣的话,我们可以一起来看看项目中的代码,其实很简单,但还是简单一起看一下
项目目录说明
|-- Hook-Http-Request
|-- .env
|-- package.json
|-- README.md
|-- vite.config.ts
|-- .vscode
| |-- extensions.json
|-- apifox
| |-- 动态路由.apifox.json
|-- public
| |-- favicon.ico
|-- src
| |-- App.vue
| |-- main.ts
| |-- api
| | |-- common
| | |-- index.ts
| |-- utils
| |-- http
| |-- index.ts
| |-- README.md
| |-- request.ts
|-- typings
|-- utils
|-- http
|-- index.d.ts
重点文件
- apifox:接口相关文件
- api: 接口请求文件
- utils/http:http请求工具类
- request.ts: 基本配置,这里对请求回来的数据蜕了两层,做的很简单,因为不是重点。具体情况看你的项目需求
- index.ts: 创建请求的工厂函数
- README:使用说明书
- typings/utils/http/index.d.ts: 类型文件
dealwithQuery
/**
* 将ref声明的参数处理成正常参数,而不是对象
* @param query
* @returns
*/
function dealwithQuery<Request>(query?: HttpRequestQueryType<Request>) {
let params: { [key: string]: any } = {};
if (query) {
for (const key in query) {
if (Object.prototype.hasOwnProperty.call(query, key)) {
const info = query[key];
params[key] = isRef(info) ? info.value : info;
}
}
}
return params;
}
此方法,主要是处理使用ref包裹过来的参数,保证参数的正确性
isRef: 判断是否为Ref包裹的数据
createHttpGetFactory
export function createHttpGetFactory<
Request extends { [key: string]: Ref<any> | any },
Response
>(url: string, type: "get" | "delete" = "get", options: AxiosRequestConfig<Request> = {}) {
// 生产一个hook函数
return function useGetRequest<Transform = Response>({
enabled,
query,
watchKeys,
onSuccess = (data: any) => data,
onError = (_error: any) => { },
onFinally = (stopLoading: () => void) => stopLoading(),
}: CreateHttpGetFactoryRequest<
Request,
Response,
Transform
>): CreateHttpGetFactoryResponse<Request, Response, Transform> {
// tips1: 定义三个变量作为结构内容使用
const loading = ref(false);
const error = ref(false);
const data = ref<Transform | null>(null);
// 取消请求
let abortController: AbortControllerType = {
cancel: () => { },
target: null,
};
// tips2: 发起请求获取数据
const getDataToServer = (
params?: Request
): Promise<[Response | Transform, Response] | void> => {
abortController.target = new AbortController();
abortController.cancel = () => abortController.target?.abort();
loading.value = true;
const queryInfo = dealwithQuery(query);
const paramsInfo = dealwithQuery(params);
return httpRequest[type](url, {
params: { ...paramsInfo, ...queryInfo },
signal: abortController.target.signal,
...options,
})
.then((res: any) => {
let dealwithData = onSuccess(res);
data.value = dealwithData;
return [dealwithData, res] as [Response | Transform, Response];
})
.catch((_error) => {
error.value = true;
onError(_error);
})
.finally(() => {
onFinally(() => (loading.value = false));
});
};
// tips3: 传入新参数 重新请求
const execute = (query?: Request) => getDataToServer(query);
const enabledIsBol = typeof enabled === "boolean";
// tips4: 监听参数改变时重新发请求
watch<Request, true>(
() => {
let watchInfo: any = {};
watchKeys?.forEach((key: string) => {
watchInfo[key] = query[key];
});
return watchInfo;
},
(newVal, oldVal) => {
const isRequest = enabledIsBol ? enabled : enabled(newVal, oldVal);
if (isRequest) {
getDataToServer(newVal);
}
},
{
immediate: true,
deep: true,
}
);
return [
{
loading: loading,
data: data,
error: error,
},
{ execute, abortController },
];
};
}
tips1: 定义三个变量作为结构内容使用
这里主要定义loading,error, data, abortController四个变量
- loading:加载标识符
- error:加载错误的标识符
- data:数据源
- abortController:请求控制对象,用于请求取消等操作
tips2: 发起请求获取数据
这里主要是看onSuccess回调,这个回调将onSuccess返回的值赋值给了data,然后return 一个数组【[onSuccess处理后数据,原始数据]】, 返回值主要是服务于execute
tips3: 传入新参数 重新请求
这里对外提供一个execute方法,这个方法主要是可控请求,并且传入参数会覆盖当前参数。
tips4: 监听参数改变时重新发请求
get请求一般是属于获取数据,主要就是看参数的变化来触发请求,所以根据提供的watchKeys直接监听参数变化,变化时就重新请求,但是也有时候参数变化到某个值得时候并不想请求。就需要查看enabled参数是否为true。
难点:
这里唯一的难点其实是类型推断上,如何让类型推断从useMenuList
中的Response
类型到onSuccess
的Transform
类型,这里主要是让Transform = Response
,然后data类型定义为Ref<Transform>
。
createHttpPostFactory
export function createHttpPostFactory<
Request extends { [key: string]: Ref<any> | any },
Response
>(url: string, type: "post" | "put" = "post", options: AxiosRequestConfig<Request> = {}) {
// 生产一个hook函数
return function usePostRequest<Transform = Response>({
query,
successTips = "请求成功",
onSuccess = (_data: Response) => _data,
onError = (_error) => { },
onFinally = (stopLoading) => stopLoading(),
}: CreateHttpPostFactoryRequest<
Request,
Response,
Transform
>): CreateHttpPostFactoryResponse<Request, Response, Transform> {
// 定义三个变量作为结构内容使用
const loading = ref(false);
const error = ref(false);
// 取消请求
let abortController: AbortControllerType = {
cancel: () => { },
target: null,
};
const postDataToServer = (
params?: Request,
tips?: successTipsType
): Promise<[Response | Transform, Response] | void> => {
abortController.target = new AbortController();
abortController.cancel = () => abortController.target?.abort();
loading.value = true;
const queryInfo = dealwithQuery(query);
const paramsInfo = dealwithQuery(params);
return httpRequest[type]<ResponseDataType<Response>>(
url,
{ ...paramsInfo, ...queryInfo },
{
signal: abortController.target.signal,
...options,
}
)
.then((res: any) => {
if (tips || successTips) {
alert( "请求成功");
}
const dealwithData = onSuccess(res);
return [dealwithData, res] as [Response | Transform, Response];
})
.catch((_error) => {
error.value = true;
onError(_error);
})
.finally(() => {
onFinally(() => (loading.value = false));
});
};
// 传入新参数 重新请求
const execute = (query?: Request, tips?: successTipsType) =>
postDataToServer(query, tips);
return [
{
loading: loading,
error: error,
},
{ execute, abortController },
];
};
}
post请求大部分同get请求,但是因为post往往是手动控制,所以这里并不提供监听这种操作
post请求后一般情况下都会有一个成功提示,所以这边就默认给了一个提示能力
最后
大致实现就是这样,其实整体代码实现并不难,唯一难点就是类型推断上,也可能是我对ts不熟悉导致的。
其实现在已经有实现该功能的库了:【alova】
不过又有什么关系呢!我们自己实现是不是更灵活呢!哈哈哈