Basado en encapsulación TS de axios y generación automática de api

TSEmpaquetado basado en pares axiosy generación apiautomá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 axiosel paquete

1.1 Hablemos de por qué necesitamos encapsular

  1. El módulo de red se extrae para proporcionar un punto de administración unificado

    • En la Figura 1, lo 组件ABChemos usado en , axiosy ABCnecesitamos introducirlo axiosy usarlo en , respectivamente. La desventaja de esto es que

      • Introducir la misma dependencia repetidamente

      • axiosExiste una fuerte dependencia de cada componente y el mantenimiento es difícil cuando axiosse produce una actualización y se detiene el mantenimiento o hay un problema BUGimportante .

      • axiosLa 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:

        imagen.png

    • 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 vezaxios
      • 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:

      imagen.png

  2. 避免 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(),就是中断一个已被发出的请求。

imagen.png

axiosHa sido encapsulado para nosotros. signalPodemos cancelTokencancelarlo 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()
复制代码

Swagger2. Genere TSmódulos directamente a través de la generación de documentos.API

La encapsulación me axioshizo 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 swaggerdocumento o OpenApila información proporcionada por el backend, todo el contenido encapsulado se puede generar directamente, puede ver la siguiente imagen

generar.webp

imagen.png

jp-api.gif

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 GitHubalmacén issues.

Para uso específico, en README

Dirección del almacén: jp-axios

Si te gusta, por favor dale una estrella, gracias

Supongo que te gusta

Origin juejin.im/post/7083434985356525598
Recomendado
Clasificación