首页 > 其他分享 >TS 封装 Axios

TS 封装 Axios

时间:2023-02-01 09:00:08浏览次数:60  
标签:Axios 封装 string TS 泛型 axios 类型 config

前言

Axios 的二次封装是一项基础工作,主要目的就是将一些常用的功能进行封装,简化后续网络请求的发送。

JS 版本的封装大家都已经非常熟悉了,可以信手拈来。但是使用 TypeScript 对 Axios 进行封装,稍微就复杂了些。主要是由于 TS 引入了类型系统,带来了一些类型的束缚。对于 TS 不太熟悉的小伙伴就容易绕晕。

因此本文适合的阅读对象:熟悉 axios 封装,但在 TypeScript 中不知该如何下手。

明确我们的封装目标:能用上TypeScript 带来的好处,类型检查(安全性) 和语法提示(便捷性) 。

本文将从泛型入手,然后了解 Axios 中的部分类型,延续 JS 版本的极简风,教你封装出一个可用的清爽版 Axios。

初始化项目

使用 create-vue 脚手架初始化项目:

  1.   npm create vue ts-axios
  2.   // 或者
  3.   pnpm create vue ts-axios
  4.   复制代码

选择添加 TypeScript :

cfc493c04238995f80e43236a1b03828.jpegimage-20221015234134695

之后进入项目目录并安装依赖:

  1.   npm install
  2.   // 或者
  3.   pnpm install
  4.   复制代码

安装其他依赖

示例会用到一些组件,所以安装下组件库:

  1.   pnpm add axios
  2.   pnpm add ant-design-vue
  3.   复制代码

接口 mock

使用插件 mock 一个登录接口和一个用户信息接口,方便测试请求。

上篇文章《一个登录案例包学会 Pinia》[2]介绍了在 vite 中 mock 数据的简单用法,本文不再赘述。

安装插件并进行配置:

  1.   pnpm add vite-plugin-mock mockjs -D
  2.   复制代码
  1.   // vite.config.ts
  2.    
  3.   import vue from '@vitejs/plugin-vue'
  4.   import { viteMockServe } from 'vite-plugin-mock'
  5.    
  6.   export default defineConfig({
  7.     plugins: [
  8.       vue(),
  9.       viteMockServe()
  10.     ],
  11.    
  12.   })
  13.   复制代码

建立 mock 文件,一个登录接口返回 token,一个用户信息接口返回用户名等信息,还有一个模拟出现业务错误的接口。

  1.   // mock/user.ts
  2.    
  3.    
  4.   export default [
  5.     // 用户登录
  6.     {
  7.       url: "/api/user/login",
  8.       method: "post",
  9.       response: (res) => {
  10.         return {
  11.           code: 0,
  12.           message: 'success',
  13.           data: {
  14.             token: "Token"
  15.           }
  16.         }
  17.       }
  18.     },
  19.     // 获取用户信息
  20.     {
  21.       url: "/api/user/info",
  22.       method: "get",
  23.       response: (res) => {
  24.         return {
  25.           code: 0,
  26.           message: 'success',
  27.           data: {
  28.             id: "2467751560226270",
  29.             username: "昆吾kw",
  30.             avatar: "https://p3-passport.byteimg.com/img/user-avatar/3745b7eb198f2357155cd88eb7930f35~180x180.awebp",
  31.             description: "前端开发",
  32.           }
  33.         }
  34.       }
  35.     },
  36.    
  37.     // 一个失败的请求
  38.     {
  39.       url: "/api/error",
  40.       method: "get",
  41.       response: (res) => {
  42.         return {
  43.           code: 1,
  44.           message: '密码错误',
  45.           data: null
  46.         }
  47.       }
  48.     }
  49.   ]
  50.   复制代码

泛型

泛型的英文是Generic,意思是“通用的”,“一般的“。

在程序设计,泛型中可以理解为 “泛指的类型”,不确定的类型,或者通用的类型。

理解泛型

泛型的概念很像函数中的参数。为了更方便的理解什么是泛型,可以用函数参数做一个类比。

函数在定义时,我们并不知道参数的值到底是多少。只有当函数调用时,才能确定。

  1.   function print(a) {
  2.       console.log(a)
  3.   }
  4.    
  5.   print('hello')
  6.   print('world')
  7.   复制代码

泛型的含义也是如此。只不过,参数不确定的是值,泛型不确定的是类型。

泛型的使用场景有很多种,比如泛型函数,泛型类,泛型接口。我们只看泛型函数。

如何定义泛型函数呢?如下:

  1.   function add<T>(a:T, b:T): T {
  2.       return a + b
  3.   }
  4.   复制代码

这段代码定义了一个 add 函数,在函数名后来使用了一对尖括号来声明泛型,使用 T(Type 的缩写)来表示一个泛型。之后参数的类型,就不再具体指定是 string 还是 number ,亦或是其他具体的类型,而是用 T 表示。函数的返回值类型同样使用 T 表示。

这样就声明了一个泛型函数。

接着就是泛型函数的调用:

  1.   add<number>(1, 2)
  2.    
  3.   add<string>('a', 'b')
  4.   复制代码

和普通函数的调用一样,只不过在函数名后面再次使用一对尖括号,来传递了具体的类型。

通过和参数的类比,详细大家能体会到泛型的含义了。所谓泛型,就是在定义时不确定,而在使用时确定的一种类型。泛型的好处就是提高了代码的复用性,减少了代码的冗余。

有了对泛型的初步认识后,下面就可以去进入正题,去看看 TypeScript 如何封装 Axios 了。

本文的思路

我们不再按照创建实例、配置参数、设置拦截器、封装公共请求方法、封装 API 的顺序去实现封装代码。而是打破常规,从类型入手。

Axios 中的重要类型

查看第三方库的类型声明

Axios 提供了完备的类型声明。声明文件可以直接去看 node_modules/axios/index.d.ts 这个文件:

31371802fcd0b60e1d612439fdad9e2f.jpegimage-20221015222400669

也可以在编辑器中,按 ctrl,同时鼠标点击 axios 模块跳转过去:

3830c028a59e957b6b69d9b4e7f5e93b.jpegimage-20221015222331509a891d437f6f037153b759e1e3efab91d.jpegimage-20221015222440898

要封装好 axios,需要先明白这几个类型是干啥用的。

从一个请求方法入手,看常用的类型

TypeScript 中 class 不仅是类,还可以是类类型。作为前者,它是一个值。作为后者,它是一个类型。

下面的 Axios 就是一个类类型,它声明了一些成员的类型。我们看其中的核心请求方法,requestget ,post :

  1.   export class Axios {
  2.     // ......
  3.     request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D>): Promise<R>;
  4.       
  5.     get<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
  6.       
  7.     post<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
  8.     
  9.     // ......
  10.   }
  11.   复制代码

熟悉 axios 的朋友都知道,axios.getaxios.post 这些方法其实是对 axios.request 的一层封装。所以我们就看 request 方法的声明。

request 方法有三个泛型,T ,R 和 D,接收一个 AxiosRequestConfig 类型的参数作为配置对象,返回值的类型是一个接收泛型R 的 Promise 类型。

  1.   request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D>): Promise<R>;
  2.   复制代码

这三个泛型,T 的含义是什么?要去看 R。R 的含义是什么?要去看它的默认类型 AxiosResponse :

  1.   export interface AxiosResponse<T = any, D = any> {
  2.    data: T;
  3.    status: number;
  4.    statusText: string;
  5.    headers: RawAxiosResponseHeaders | AxiosResponseHeaders;
  6.    config: AxiosRequestConfig<D>;
  7.    request?: any;
  8.   }
  9.   复制代码

看到这个结构,我们知道了,原来 AxiosResponse 就是在设置响应拦截器中用到的那个 response 对象的类型。

同时也就知道了泛型 T 就是服务器返回的数据的类型。因为服务器究竟返回什么类型,代码是不知道的,所以它的默认类型是 any。

回到头来再来看 request 方法的定义,现在就能明白了,它接收的第一个泛型 T 就是将来服务器返回数据的类型,R 就是这个数据经过 axios 包装一层得到的 response 对象的类型,而 request 方法的返回值是一个 Promise,其值就是成功态的 R,也就是 response对象。

第三个泛型 D,这里就不再说了。你可以用同样的方法,找出它又是谁的类型,然后发在评论区。

AxiosRequestConfig 类型

先看下它的类型声明:

  1.   export interface AxiosRequestConfig<D = any> {
  2.     url?: string;
  3.     method?: Method | string;
  4.     baseURL?: string;
  5.     headers?: RawAxiosRequestHeaders;
  6.     // .....
  7.   }
  8.   复制代码

相信大家能看出来,它其实就是 axios 的配置对象的类型。在设置请求拦截器和封装公共请求方法时会用到。

AxiosInstance

该类型为 axios.create 方法创建出的实例的类型。后面直接用到。

AxiosError

该类型为请求发送过程中出现错误产生的错误对象的类型:

  1.   export class AxiosError<T = unknown, D = any> extends Error {
  2.     constructor(
  3.         message?: string,
  4.         code?: string,
  5.         config?: AxiosRequestConfig<D>,
  6.         request?: any,
  7.         response?: AxiosResponse<T, D>
  8.     );
  9.    
  10.     config?: AxiosRequestConfig<D>;
  11.     code?: string;
  12.     request?: any;
  13.     response?: AxiosResponse<T, D>;
  14.     isAxiosError: boolean;
  15.     status?: number;
  16.     toJSON: () => object;
  17.     cause?: Error;
  18.     // ......
  19.   }
  20.   复制代码

我们会用到其中的 response 属性,它表示响应对象,需要根据它的 HTTP 状态码做一些处理。

知道了上面这几个类型,接下来就可以着手封装 Axios 了。

开始封装

先提前打个预防针,因为封装的很简单,没有用到类,没有过多的处理业务逻辑。估计看完后你会感觉索然无味。

毕竟我们最初的目的就是用上 TypeScript 的类型能力:类型检查和代码提示。

而且我相信,太复杂的封装,可能会阻碍对类型系统的学习,反而得不偿失。

新建一个 src/utils/request.ts 文件,在这个文件中对 Axios 进行封装。

Result 类型

从服务器返回的数据的类型通常长这样子:

  1.   {
  2.      code: 0,
  3.      message: 'ok',
  4.      data: {} 
  5.   }
  6.   复制代码

定义一个接口 Result 来表示它,其中 data 的类型不确定,所以就要用到泛型了:

  1.   /* 服务器返回数据的的类型,根据接口文档确定 */
  2.   export interface Result<T=any> {
  3.     code: number,
  4.     message: string,
  5.     data: T
  6.   }
  7.   复制代码

这是一个泛型接口。和泛型函数差不多,在定义时声明一个泛型,用这个泛型去规范成员的类型。将来使用该接口的时候,再去确定泛型的具体类型。

接下来就可以写代码了。

创建实例

这个大家都非常熟悉了,不再赘述。

  1.   import axios from 'axios'
  2.   import type { AxiosInstance } from 'axios'
  3.    
  4.   const service: AxiosInstance = axios.create({
  5.     baseURL: '/api',
  6.     timeout: 30000
  7.   })
  8.   复制代码

请求拦截器

这个大家也很熟悉,主要在这里处理请求发送前的一些工作,比如给 HTTP Header 添加 token ,开启 Loading 效果,设置取消请求等。

  1.   import type { AxiosError, AxiosRequestConfig } from 'axios'
  2.   import { message as Message } from 'ant-design-vue'
  3.    
  4.   /* 请求拦截器 */
  5.   service.interceptors.request.use((config: AxiosRequestConfig) => {
  6.     //  伪代码
  7.     // if (token) {
  8.     //   config.headers.Authorization = `Bearer ${token}`;
  9.     // }
  10.     return config
  11.   }, (error: AxiosError) => {
  12.     Message.error(error.message);
  13.     return Promise.reject(error)
  14.   })
  15.   复制代码

响应拦截器

响应拦截器大家也很熟悉了。简单说一下做了哪些事情:

  • 根据自定义错误码判断请求是否成功,然后只将组件会用到的数据,也就是上面的 Result 中的 data 返回

  • 如果错误码判断请求失败,此时为业务错误,比如用户名不存在等,在这里进行提示

  • 如果网络错误,则进入第二个回调函数中,根据不同的状态码设置不同的提示消息进行提示

  1.   import type { AxiosError, AxiosResponse } from 'axios'
  2.   import { message as Message } from 'ant-design-vue'
  3.    
  4.   /* 响应拦截器 */
  5.   service.interceptors.response.use((response: AxiosResponse) => {
  6.     const { code, message, data } = response.data
  7.    
  8.     // 根据自定义错误码判断请求是否成功
  9.     if (code === 0) {
  10.       // 将组件用的数据返回
  11.       return data
  12.     } else {
  13.       // 处理业务错误。
  14.       Message.error(message)
  15.       return Promise.reject(new Error(message))
  16.     }
  17.   }, (error: AxiosError) => {
  18.     // 处理 HTTP 网络错误
  19.     let message = ''
  20.     // HTTP 状态码
  21.     const status = error.response?.status
  22.     switch (status) {
  23.       case 401:
  24.         message = 'token 失效,请重新登录'
  25.         // 这里可以触发退出的 action
  26.         break;
  27.       case 403:
  28.         message = '拒绝访问'
  29.         break;
  30.       case 404:
  31.         message = '请求地址错误'
  32.         break;
  33.       case 500:
  34.         message = '服务器故障'
  35.         break;
  36.       default:
  37.         message = '网络连接故障'
  38.     }
  39.     
  40.     Message.error(message) 
  41.     return Promise.reject(error)
  42.   })
  43.   复制代码

公共请求方法

这里是本文的重点,也是 TS 封装 Axios 的重点。使用 TS 无非要获得良好的代码提示,我们在调用接口时编辑器能提示出该接口的返回值有哪些。

  1.   /* 导出封装的请求方法 */
  2.   export const http = {
  3.     get<T=any>(url: string, config?: AxiosRequestConfig) : Promise<T> {
  4.       return service.get(url, config)
  5.     },
  6.    
  7.     post<T=any>(url: string, data?: object, config?: AxiosRequestConfig) :Promise<T> {
  8.       return service.post(url, data, config)
  9.     },
  10.    
  11.     put<T=any>(url: string, data?: object, config?: AxiosRequestConfig) :Promise<T> {
  12.       return service.put(url, data, config)
  13.     },
  14.    
  15.     delete<T=any>(url: string, config?: AxiosRequestConfig) : Promise<T> {
  16.       return service.delete(url, config)
  17.     }
  18.   }
  19.   复制代码

我们以 get 方法为例,看下为什么这么封装?

  1.   get<T>(url: string, config?: AxiosRequestConfig) : Promise<T> {
  2.       return service.get(url, config)
  3.   }
  4.   复制代码

上文说过,axios 的 requet,get 这些方法,返回的都是一个 AxiosResponse 类型的数据。

默认的响应拦截器返回的是完整的 response 对象,类型为 AxiosResponse 。

经过我们的修改,现在响应拦截器只返回 response.data.data,对应的类型就是 AxiosResponse<Result> 中的 T。所以这里的 service.get 方法,其实际返回类型应为 T,但是编辑器并不知道,它仍然认为它返回的是:

47e9c66a7ad20a76b1fe9fddfd735002.jpegimage-20221016134207024

所以我们要手动帮助编辑器“修正”类型提示,也就是不依靠 TS 的类型推导,而是主动设置返回类型为 Promise :

  1.   get<T>(url: string, config?: AxiosRequestConfig) : Promise<T> {
  2.       return service.get(url, config)
  3.   }
  4.   复制代码

这样编辑器就准确识别类型了:

6f76dbd5550fce1b4d3557e5235759df.jpegimage-20221016134438155

这里的泛型 T 表示的服务器返回数据的类型,具体返回什么类型,要到接口调用时才知道。所以下面来到 API 层,去封装请求接口。

为什么要封装公共请求方法

之所以封装这一层公共请求方法,主要目的就是为了帮编译器正确识别类型。方法就是手动设置返回类型。如果直接使用 axios 实例的请求方法,就需要在每次调用时都指定一遍,当接口有几十上百个时,多少会在增加一些工作量。所以就在这里多加一层,提前将类型制定好。

API 层

准备两个文件,一个写类型,一个写接口。

dac67da8cf42a50cd5414c8271e81a52.jpegimage-20221016135035587

在 api/user/types.ts 文件中,写接口需要的参数的类型,和接口返回数据的类型。这里的类型,要根据接口文档而定。

  1.   /* 登录接口参数类型 */
  2.   export interface LoginData {
  3.     username: string,
  4.     password: string,
  5.   }
  6.    
  7.   /* 登录接口返回值类型 */
  8.   export interface LoginRes {
  9.     token: string
  10.   }
  11.    
  12.   /* 用户信息接口返回值类型 */
  13.   export interface UserInfoRes {
  14.     id: string,
  15.     username: string,
  16.     avatar: string,
  17.     description: string,
  18.   }
  19.   复制代码

在 api/user/index.ts 文件中,封装组件用到的接口。

  1.   import request, { http } from '@/utils/request'
  2.    
  3.   import type { LoginData, LoginRes, UserInfoRes} from './types'
  4.    
  5.   /**
  6.    * 登录
  7.    */
  8.   export function login(data: LoginData) {
  9.     return http.post<LoginRes>('/user/login', data);
  10.   }
  11.    
  12.   /**
  13.    * 获取登录用户信息
  14.    */
  15.   export function getUserInfo() {
  16.     return http.get<UserInfoRes>('/user/info')
  17.   }
  18.   复制代码

以 login 方法为例进行说明。它接收 LoginData 类型的参数作为请求参数。在调用接口时,通过泛型指定了该接口的返回值类型:

79a25a394a0967958eeda059736f6329.jpegimage-20221016135528697

测试

Axios 代码提示测试

App.vue 组件中有一个登录表单,在这里测试登录接口是否正常。

方法login 可以推导出当前返回值的类型:

30e418bf5cfdf29282601ab6e97081d0.jpegimage-20221016002008786

返回值 res 的类型也能推导出:

423253db95356432be89d31b6e6107d0.jpegimage-20221016002133845

也可以正确使用类型提示:

c91c71d89295055891eab6255b69cbc7.jpegimage-20221016002233899

然后再测试下调用请求用户信息的接口:

d90e7966460116f692aa5ee4320165f1.jpegimage-20221016002441196

如我们所愿,现在 Axios 经过 TypeScript 的封装,可以给出友好的类型提示,这对于开发体验和效率都有很好的提升。

响应拦截器提示测试

测试业务处理错误的请求,假设此次请求用户密码输入错误:

030bf2c0e50cbaf9e25e60ebba71ea2a.jpegimage-20221016132121313

关闭开发服务,测试无网络连接下的请求发送:

1f165df7078ca028c2c07f2b3be71654.jpegimage-20221016132156024

小结

本文示例代码已上传到仓库[3]

开始只是想说说 TypeScript 封装 Axios 的一个简单实现版本。但没想到最后说了不老少内容,包括:

  • TypeScript 泛型的概念

  • 第三方库如何看类型声明

  • 如何利用类型封装 Axios

 https://blog.csdn.net/lunahaijiao/article/details/128246274

 

 

 

标签:Axios,封装,string,TS,泛型,axios,类型,config
From: https://www.cnblogs.com/yinhao-jack/p/17081383.html

相关文章

  • mybits_基础
    1.框架:一款半成品软件,我们可以基于框架继续开发,从而完成一些个性化的需求2.ORM:对象关系映射,数据和实体对象的映射3.MyBatis:是一个优秀的基于Java的持久层框架,它内部封......
  • uniapp 项目的 echarts 图表本地可以展示,同事打包后 echarts 图表无法显示
    造成问题的原因本地开发环境装了百度图表echarts插件,代码提交SVN后,同事获取下来打包发布,发布后发现线上的图表无法加载出来这个同事专门负责发版本之类的,减少生产......
  • Hibernate 自动进行数据封装
    1.前言Hibernate可以构建各种复杂的SQL语句,但其本质都是反射机制结合映射关系完成的。框架也仅是一款程序产品,人为编写的产物。要相信,只要你愿意,你完全可以实现自己的......
  • 23/1/31-LeetCode 21: Merge Two Sorted Lists
    MergeTwoSortedLists思路bug注意,一开始我写的是ListNode*ans,*cur;if(list1->val<=list2->val){ ans=cur=list1; list1=list1->next;}else{ ans=......
  • 接口自动化测试|Requests库的安装与介绍
    Requests:Requests模块简介与安装Requests模块简介在python的标准库中,虽然提供了urllib,utllib2,httplib,但是做接口测试,requests使用更加方便快捷,正如官方说的,“让HTTP服务人......
  • echarts官网文档打开慢的解决方法
    echarts官网文档打开慢的解决方法由于我们在做大数据屏的时候需要很多echarts图表,这个过程中也会遇到需要查询echarts官网文档、手册、配置项的时候,但是由于网站在国外,访问......
  • 上传文件报错:The field files exceeds its maximum permitted size of 1048576 bytes
    SpringBootconfiguresSpringMVCwithamaximumfileof1Mbperfileandamaximumof10Mboffiledatainasinglerequest文件上传有默认最大限制,即最大可支持......
  • 特征工程——数据的标准化(Z-Score,Maxmin,MaxAbs,RobustScaler,Normalizer)
    数据标准化是一个常用的数据预处理操作,目的是处理不同规模和量纲的数据,使其缩放到相同的数据区间和范围,以减少规模、特征、分布差异等对模型的影响。比如线性回归模型、......
  • Axios的使用
    导入axios.js的脚本:<scriptsrc="js/axios.js"></script>对于get方式:直接在url后面添加参数和值<script>axios({method:"get",url:"http://loc......
  • TTStand 常见问题【F.A.Q】
    FrequentlyAskedQuestions 问题1:打开TestStand或者TTStand时,显示调用组件失败?答1:TestStand部分组件文件丢失导致,可以修复TestStand或者卸载重装TestStand解决。 ......