# hook-http-request **Repository Path**: wangzhaoyv/hook-http-request ## Basic Information - **Project Name**: hook-http-request - **Description**: 使用hook的方式改变请求 - **Primary Language**: TypeScript - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-06-24 - **Last Updated**: 2023-07-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 请求新姿势 在以前的请求中,我们需要定义`loading,error,data等字段`来接收请求回来的数据,然后通过自己对data的处理获取对应的格式。我尤其是不喜欢`loading,和error的定义`,因为它是一个不影响我逻辑的代码,但是占用了声明和赋值两个地方,那么有没有办法解决这些问题呢?随着Vue3的兴起,我们可以利用新特性来一波更新,先来看看下面的使用文档,你是否中意! ### [【Git 项目地址 】](https://gitee.com/wangzhaoyv/hook-http-request) ### HTTP 请求 ```js |-- http // 请求相关内容 |-- request.ts // 请求初始化配置 |-- index.ts // 导出GET,POST请求方式 ``` ## 使用文档 [使用文档](./src/utils/http/README.md) #### 如果你对上面的请求方式感兴趣的话,我们可以一起来看看项目中的代码,其实很简单,但还是简单一起看一下 #### 项目目录说明 ```bash |-- 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 ```typescript /** * 将ref声明的参数处理成正常参数,而不是对象 * @param query * @returns */ function dealwithQuery(query?: HttpRequestQueryType) { 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 ```typescript export function createHttpGetFactory< Request extends { [key: string]: Ref | any }, Response >(url: string, type: "get" | "delete" = "get", options: AxiosRequestConfig = {}) { // 生产一个hook函数 return function useGetRequest({ enabled, query, watchKeys, onSuccess = (data: any) => data, onError = (_error: any) => { }, onFinally = (stopLoading: () => void) => stopLoading(), }: CreateHttpGetFactoryRequest< Request, Response, Transform >): CreateHttpGetFactoryResponse { // tips1: 定义三个变量作为结构内容使用 const loading = ref(false); const error = ref(false); const data = ref(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( () => { 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`。 ### createHttpPostFactory ```typescript export function createHttpPostFactory< Request extends { [key: string]: Ref | any }, Response >(url: string, type: "post" | "put" = "post", options: AxiosRequestConfig = {}) { // 生产一个hook函数 return function usePostRequest({ query, successTips = "请求成功", onSuccess = (_data: Response) => _data, onError = (_error) => { }, onFinally = (stopLoading) => stopLoading(), }: CreateHttpPostFactoryRequest< Request, Response, Transform >): CreateHttpPostFactoryResponse { // 定义三个变量作为结构内容使用 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]>( 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】](https://alova.js.org/zh-CN/) 不过又有什么关系呢!我们自己实现是不是更灵活呢!哈哈哈