基于 TS
对 axios
的封装和 api
自动生成
一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 1 天,点击查看活动详情。
本文内容为自己练习封装和一点点自己的想法, 而做的一个库. 如果你喜欢, 或者觉得对你有用, 能帮助到你,那我就很开心了.
本文仓库: jp-axios
一.基于 axios
的封装
1.1 先说说为什么要封装
-
抽离网络模块统一提供管理点
-
在图一中, 我们分别在
组件ABC
中使用到了,axios
, 我们需要分别要在ABC
中引入axios
进行使用,这样的缺点在于-
重复引入相同依赖
-
在每个组件内对
axios
进行了强度依赖, 当axios
发生升级停止维护或者有重大BUG
的时候,维护难度大 -
每次调用
axios
所使用的的请求地址,方式,参数等,可能存在相同, 但存在与多个调用点, 更新难度大图一:
-
-
在图二中,我们可以再封装层中, 提供请求方式, 将所有的接口统一在这里管理,然后提供给不同组件调用,这样的好处是
- 使用的封装层是自身项目代码,没有第三方依赖度, 只有封装层依赖了一次
axios
- 每个组件的请求方式统一,且仅仅只需要维护封装层,就可以达到维护了所有使用到封装方法的组件
图二:
- 使用的封装层是自身项目代码,没有第三方依赖度, 只有封装层依赖了一次
-
-
避免
axios
或者使用的第三方网络请求依赖, 停止维护,或者发生重大BUG
, 自身团队又无法解决的时候,需要更换时, 升级更新维护难度飙升的情况, 就像现在浏览器都在发展fetch
, 我们要知道,所有的东西,在官方声明后,之前的产物都会退出历史舞台,就比如co
, 推出async/await
之后, 就退出了历史舞台了
1.2 封装 `axios
1.2.1 基础封装
第一步: 增强 axios
的配置参数
// ./types/index.ts
/**
* @description 扩展 `AxiosRequestConfig`, 使用时可以传递专属拦截器
*/
export interface JPRequestConfig<T = JPResponse, K = JPResponse> extends AxiosRequestConfig {
/**
* @description 拦截器
*/
interceptors?: JPInterceptors<T, K>
}
/**
* @description 封装拦截器接口
* 响应拦截其中,我们提供两个泛型参数,
* 因为有时候我们入参是被 `axios` 包装的响应对象,
* 但是需要返回它的 `data` 属性,才是我们接口的实际返回值
*/
export interface JPInterceptors<T = JPResponse, K = JPResponse> {
requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig
requestInterceptorCatch?: (err: any) => any
responseInterceptor?: (res: T) => K
responseInterceptorCatch?: (err: any) => any
}
/**
* @description 将响应类型给外部使用,统一提供,避免导出引入`AxiosResponse`
*/
export interface JPResponse<T = any> extends AxiosResponse<T, any> {}
/**
* @description 判断拦截器的入参和出参
*/
export type Interceptor<T, K extends boolean = false> = K extends true ? JPResponse<T> : T
复制代码
第二步: 使用 class
进行类封装, 更方便创建多个实例,适用范围更广,封装性更强一些。
// ./jp-axios.ts
import axios from 'axios'
import type { AxiosInstance } from 'axios'
import type { JPRequestConfig, JPResponse, JPInterceptors, Interceptor } from './types'
class JPAxios {
instance: AxiosInstance
// 实例拦截器, 第一组实力拦截器,类型必然是`axios`包装的类型,所以用`JPResponse<T>`包裹
constructor(config?: JPRequestConfig<JPResponse<T>, T>) {
// 1.创建实例,并保存
this.instance = axios.create(config)
this.interceptors = config?.interceptors
// 定义的实例拦截器
config?.interceptors && this.use(config?.interceptors as any)
}
// 封装统一请求的方法
request(config: JPRequestConfig): Promise<T> {
// ...
}
// 封装拦截器注册方法
// 泛型T: 响应拦截器出参类型
// 泛型K: 表示入参是否是`axios`的响应包装类型
use<T = JPResponse, K extends boolean = false>(interceptors: JPInterceptors<Interceptor<T, K>, T>) {
/**
* Tips: 拦截器执行结构
* - Q:请求 S: 响应 F: 服务器
* - 如果对顺序要求,可以通过设置对应的拦截顺序进行修改,机制如下
* - Q1/S1 拦截器1
* - Q2/S2 拦截器2
* - Q3/S3 拦截器3
*
* F F 服务器响应
* Q1 ↑ S1 ↓
* Q2 ↑ S2 ↓
* Q3 ↑ S3 ↓
* 浏览器发送
*/
// 实例请求拦截器
this.instance.interceptors.request.use(
interceptors?.requestInterceptor,
interceptors?.requestInterceptorCatch
)
// 实例响应拦截器
this.instance.interceptors.response.use(
interceptors?.responseInterceptor as any,
interceptors?.responseInterceptorCatch
)
}
}
复制代码
第三步: 封装统一请求方法, 作为其他方法模板
// ./jp-axios.ts
class JPAxios {
// ...
// 封装统一请求的方法,作为模板
request<T = JPResponse>(config: JPRequestConfig<T, T>): Promise<T> {
return new Promise((resolve, reject) => {
// 定义请求拦截器
if (config.interceptors?.requestInterceptor)
config = config.interceptors?.requestInterceptor(config)
// 进行请求
this.instance
.request<any, T>(config)
.then((res) => {
// 定义接口响应拦截器
if (config.interceptors?.responseInterceptor)
res = config.interceptors?.responseInterceptor(res)
resolve(res)
})
.catch((err) => {
reject(err)
})
})
}
get<T = JPResponse>(config: JPRequestConfig<T, T>): Promise<T> {
return this.request({ ...config, method: 'GET' })
}
// ...
}
复制代码
在基础封装完毕之后, 我们为所有的实例提供了多种方式的拦截器, 分别是实例拦截器, 接口拦截器, 让接口的可控性更强了,我们可以像这样去使用它
// 1.创建实例
const instance = new JPAxios({
baseURL: 'http://localhost:3000',
timeout: 5000,
})
// 2.正常使用
instance
.get('/users')
.then((res) => {
console.log(res)
})
.catch((err) => {
console.log(err)
})
// 3.如果需要拦截器可以使用实例拦截器
const instance1 = new JPAxios({
baseURL: 'http://localhost:3000',
timeout: 5000,
interceptors: {
requestInterceptor: (config) => {
// 可以在请求前做一些处理
return config
},
requestInterceptorCatch: (err) => {
// 如果发送请求失败,可以在这里做一些处理
console.log(err)
},
responseInterceptor: (response) => {
// 可以在请求后做一些处理
return response
},
responseInterceptorCatch: (err) => {
// 如果请求失败,可以在这里做一些处理
console.log(err)
},
},
})
// 4.你也可以使用实例的方法来创建拦截器
instance1.use({
requestInterceptor(params) {
// you can do something
},
requestInterceptorCatch(params) {
// you can do something
},
responseInterceptor(params) {
// you can do something
},
responseInterceptorCatch(params) {
// you can do something
},
})
// 5.如果你需要针对单独接口做特殊处理,也可以定义接口拦截器
instance1.get({
url: '/users',
interceptors: {
requestInterceptor(params) {
// you can do something
},
requestInterceptorCatch(params) {
// you can do something
},
responseInterceptor(params) {
// you can do something
},
responseInterceptorCatch(params) {
// you can do something
}
}
})
复制代码
1.2.2 增加取消重复请求的功能
第一步: 为 JPRequestConfig
添加开关参数
// ./types/index.ts
export interface JPRequestConfig<T = JPResponse, K = JPResponse> extends AxiosRequestConfig {
/**
* @description 拦截器
*/
interceptors?: JPInterceptors<T, K>
/**
* @description 是否删除重复请求
*/
removeRepeat?: boolean
}
复制代码
第二步: 在构造函数内提供对应的实现
// ./jp-axios.ts
class JPAxios {
instance: AxiosInstance
// 当前实例开启取消重复请求功能
removeRepeat: boolean
// 存储对应请求的取消方法
private abortControllers = new Map<string, AbortController | Canceler>()
constructor(config?: JPRequestConfig<JPResponse<T>, T>) {
// 1.创建实例,并保存
this.instance = axios.create(config)
this.interceptors = config?.interceptors
// 是否开启
this.removeRepeat = !!config?.removeRepeat
if (this.removeRepeat) {
const requestInterceptor = (config: JPRequestConfig) => {
// 如果有重复的,在这里就去除掉
this.removePending(config)
// 添加下次的判定
this.addPending(config)
return config
}
const responseInterceptor = (response: JPResponse) => {
// 请求成功或失败都去除对应请求
this.removePending(response.config)
return response
}
const responseInterceptorCatch = (error: AxiosError) => {
error.config && this.removePending(error.config)
return Promise.reject(error)
}
// 建立最上层拦截器, 对相同请求,进行处理
this.use({ requestInterceptor, responseInterceptor, responseInterceptorCatch })
}
// 定义的实例拦截器
config?.interceptors && this.use(config?.interceptors as any)
}
// ...
/** ******* 根据配置提供是否删除重复请求 **********/
/**
* 生成每个请求唯一的键
*/
private getPendingKey(config: JPRequestConfig): string {
let { data } = config
const { url, method, params } = config
if (typeof data === 'string') data = JSON.parse(data) // response里面返回的config.data是个字符串对象
return [url, method, JSON.stringify(params), JSON.stringify(data)].join('&')
}
/**
* 储存每个请求唯一值, 也就是cancel()方法, 用于取消请求
*/
private addPending(config: JPRequestConfig) {
const pendingKey = this.getPendingKey(config)
config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
if (!this.abortControllers.has(pendingKey))
this.abortControllers.set(pendingKey, cancel)
})
}
/**
* 删除重复的请求
*/
private removePending(config: JPRequestConfig) {
const pendingKey = this.getPendingKey(config)
// 如果同样的请求,之前存在,则取消
if (this.abortControllers.has(pendingKey)) {
const cancelToken = this.abortControllers.get(pendingKey)
cancelToken!(pendingKey)
this.abortControllers.delete(pendingKey)
}
}
}
复制代码
1.2.3 使用
通过拦截器, 取消重复请求,我们其实就可以完成大量日常业务需求, 还有一项平常的需求是 loading
,我也提供了对应的参数, 在 JPRequestConfig
中
// ./types/index.ts
/**
* @description 扩展 `AxiosRequestConfig`, 使用时可以传递专属拦截器
*/
export interface JPRequestConfig<T = JPResponse, K = JPResponse> extends AxiosRequestConfig {
/**
* @description 拦截器
*/
interceptors?: JPInterceptors<T, K>
/**
* @description 请求状态开始,是否展示loading
*/
loading?: boolean
/**
* @description 是否删除重复请求
*/
removeRepeat?: boolean
}
复制代码
所以我们可以这样去使用
// 1.全局的`loading`什么时候开启, 大多数情况下,`loading`都在我们的组件内部,但是有时候我们需要控制全屏的 `loading` 效果, 又不是全部接口都需要,所以可以这样用
const instance = new JPAxios({
interceptors: {
requestInterceptor: (config) => {
if (config.loading) GlobalLoading.start()
return config
},
responseInterceptor: (response) => {
// 可以在请求后做一些处理
if (GlobalLoading.isStart) GlobalLoading.done()
return response
},
responseInterceptorCatch: (err) => {
// 如果请求失败,可以在这里做一些处理
if (GlobalLoading.isStart) GlobalLoading.done()
console.log(err)
},
},
})
instance.request({ url: '/user/info', loading: true }) // 这样可以再拦截器中获取到,并且针对特定接口开启全局`loading`效果了
// 2.取消重复请求
// 2.1 实例开启, 当前实例所有接口,都会检测是否重复
const instance1 = new JPAxios({ removeRepeat: true })
// 2.2 单独接口使用
instance.request({ url: '/user/info', loading: true, removeRepeat: true })
复制代码
关于取消请求:
可以看下
MDN
的描述: XMLHttpRequest ,
XMLHttpRequest
对象是我们发起一个网络请求的根本,在它底下有怎么一个方法.abort()
,就是中断一个已被发出的请求。
axios
已经为我们封装好了, 我们可以通过signal
或者cancelToken
配置,进行取消, 我感觉不需要单独封装了, 可以像这样使用, 毕竟需要取消请求的需求很少, 需要取消的也是特定的接口
const instance = new JPAxios() const controller = new AbortController() instance.request({ url: '/user/info', signal: controller.signal }) // 需要取消,则调用`abort`方法 controller.abort() 复制代码
二. 通过 Swagger
文档生直接成 TS
模块 API
通过封装 axios
让我想到一个问题, 既然封装了 axios
,提供了入参字段, 响应字段, 为什么不直接连 所有接口 一并封装好呢? 于是乎,我做了这件事情
通过后端提供的 swagger
文档或者是 OpenApi
的信息,直接生成封装好的所有内容, 可以看下下面这张图
我们可以直接生成这些, 是不是,就可以节约很多功夫呢?
所以我做了这个功能, 欢迎大家来体验一下, 可能会存在一些问题,但是这是也是自己开源的一步,有bug希望大家在评论或者 GitHub
仓库的 issues
中提出.
具体使用方法, 在 README
仓库地址: jp-axios
如果喜欢, 请给一个 star ,谢谢