TS
Empaquetado basado en pares axios
y generación api
automática
¡Acostúmbrate a escribir juntos! Este es el primer día de mi participación en el "Nuggets Daily New Plan · April Update Challenge", haz clic para ver los detalles del evento .
El contenido de este artículo es una biblioteca hecha por mí mismo para practicar la encapsulación y un poco de mis propias ideas, si te gusta o crees que te es útil y te puede ayudar, entonces estoy muy feliz.
Almacén de este artículo: jp-axios
1. Basado en axios
el paquete
1.1 Hablemos de por qué necesitamos encapsular
-
El módulo de red se extrae para proporcionar un punto de administración unificado
-
En la Figura 1, lo
组件ABC
hemos usado en ,axios
yABC
necesitamos introducirloaxios
y usarlo en , respectivamente. La desventaja de esto es que-
Introducir la misma dependencia repetidamente
-
axios
Existe una fuerte dependencia de cada componente y el mantenimiento es difícil cuandoaxios
se produce una actualización y se detiene el mantenimiento o hay un problemaBUG
importante . -
axios
La dirección de solicitud, el método, los parámetros, etc. utilizados para cada llamada pueden ser los mismos, pero hay varios puntos de llamada, lo que es difícil de actualizar.Figura 1:
-
-
En la Figura 2, podemos proporcionar el método de solicitud en la capa de encapsulación, administrar todas las interfaces aquí y luego proporcionarlas a diferentes componentes para llamar. La ventaja es que
- La capa de encapsulación utilizada es su propio código de proyecto, no hay dependencia de terceros, solo la capa de encapsulación depende una vez
axios
- El método de solicitud de cada componente está unificado, y solo es necesario mantener la capa de encapsulación para mantener todos los componentes que usan el método de encapsulación.
Figura II:
- La capa de encapsulación utilizada es su propio código de proyecto, no hay dependencia de terceros, solo la capa de encapsulación depende una vez
-
-
避免
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
Ha sido encapsulado para nosotros.signal
PodemoscancelToken
cancelarlo a través de o configuración. No creo que deba encapsularse por separado. Se puede usar así. Después de todo, hay muy pocas necesidades para cancelar la solicitud, y es también una interfaz específica que debe cancelarse.
const instance = new JPAxios() const controller = new AbortController() instance.request({ url: '/user/info', signal: controller.signal }) // 需要取消,则调用`abort`方法 controller.abort() 复制代码
Swagger
2. Genere TS
módulos directamente a través de la generación de documentos.API
La encapsulación me axios
hizo pensar en una pregunta. Dado que está encapsulada axios
, se proporcionan el campo de parámetro de entrada y el campo de respuesta, ¿por qué no conectar directamente todas interfaces y encapsularlas juntas? Entonces, hice esto
A través del swagger
documento o OpenApi
la información proporcionada por el backend, todo el contenido encapsulado se puede generar directamente, puede ver la siguiente imagen
Podemos generarlos directamente, ¿no es así? ¿Podemos ahorrarnos mucho esfuerzo?
Así que hice esta función, y le invitamos a experimentarla. Puede haber algunos problemas, pero este también es un paso de mi propio código abierto. Si hay errores, espero que pueda ponerlos en los comentarios o en el GitHub
almacén issues
.
Para uso específico, en README
Dirección del almacén: jp-axios
Si te gusta, por favor dale una estrella, gracias