vue3+ts中台项目小结之axios请求封装
公司平台新搭建了一个中台系统吧,自己在忙别的项目了,几乎没有什么参与度,作为被边缘化的成员,自己还是要努把力,多多学习,争取下进步,加油derder~!
项目是vite
搭建的,重点来看下axios
文件夹中封装的内容(axios安装过程这里就略过了)。闲话不多说,首先来看下目录结构的:
axios
├─ config
│ └─ index.ts
├─ plugins
│ ├─ axios-controller.plugin.ts
│ ├─ axios-debounce.plugin.ts
│ ├─ axios-exception.plugin.ts
│ ├─ axios-extend.plugin.ts
│ ├─ axios-path-to-regexp.ts
│ ├─ axios-retry.plugin.ts
│ └─ axios-token.plugin.ts
├─ index.ts
└─ interface.ts
就从axios
文件夹中根目录的index.ts
文件:
import { axiosExceptionPlugin } from './plugins/axios-exception.plugin'
import axios from 'axios'
import config from './config'
import { getTokenLocalApi } from '@/service/local-api/token/token.local'
import axiosProCreator from './plugins/axios-extend.plugin'
import AxiosDebouncePlugin from './plugins/axios-debounce.plugin'
import { axiosTokenPlugin } from './plugins/axios-token.plugin'
import { axiosRetryPlugin } from './plugins/axios-retry.plugin'
import { axiosPathToRegexp } from './plugins/axios-path-to-regexp'
import { axiosControllerPlugin } from './plugins/axios-controller.plugin'
const fetch = axios.create(config.defaultConfig)
const axiosPro = axiosProCreator(fetch)
/**
* 异常捕获插件
*/
axiosPro.use(axiosExceptionPlugin, config.pluginConfig.exceptionConfig)
/**
* 重试插件
*/
axiosPro.use(axiosRetryPlugin, config.pluginConfig.retryPluginConfig)
/**
* 令牌插件
*/
axiosPro.use(axiosTokenPlugin, {
tokenResource: async () => (await getTokenLocalApi())?.token,
tokenName: config.pluginConfig.tokenPluginConfig.name
})
/**
* path替换插件
*/
axiosPro.use(axiosPathToRegexp)
/**
* 请求防抖插件
*/
axiosPro.use(new AxiosDebouncePlugin())
/**
* 控制器插件
*/
axiosPro.use(axiosControllerPlugin)
export default axiosPro
分析:
- 首先来看
axios.create(自定义的配置,例如baseUrl等)
来创建一个新的axios
命名为fetch
(配置文件后面会列出)
接着来看下import config from './config'
中的配置内容:
/**
* @ Description: axios配置
*/
import {
AXIOS_REQUEST_TIMEOUT,
AXIOS_RETRIES,
AXIOS_TOKEN_NAME,
AXIOS_DELAY_TIME,
AXIOS_BASE_URL,
exceptionCode,
exceptionWhitelist
} from '@/config/axios.config'
export default {
/**
* 基础默认配置
*/
defaultConfig: {
baseURL: AXIOS_BASE_URL,
timeout: AXIOS_REQUEST_TIMEOUT
},
/**
* 插件配置
*/
pluginConfig: {
/**
* 重试插件
*/
retryPluginConfig: {
retries: AXIOS_RETRIES, // 重试次数
delayTime: AXIOS_DELAY_TIME // 每次重试之间的实践间隔
},
/**
* 令牌插件
*/
tokenPluginConfig: {
name: AXIOS_TOKEN_NAME
},
/**
* 异常捕获配置
*/
exceptionConfig: {
/**
* 异常code
*/
exceptionCode: exceptionCode,
/**
* 白名单
*/
whitelist: exceptionWhitelist
}
}
}
配置文件中,还有配置文件
import { AXIOS_REQUEST_TIMEOUT, AXIOS_RETRIES, AXIOS_TOKEN_NAME, AXIOS_DELAY_TIME, AXIOS_BASE_URL, exceptionCode, exceptionWhitelist } from '@/config/axios.config'
import type { AxiosResponse } from 'axios'
/**
* 请求超时时长(单位:毫秒)
*/
export const AXIOS_BASE_URL = '/api'
/**
* 请求超时时长(单位:毫秒)
*/
export const AXIOS_REQUEST_TIMEOUT = 20000
/**
* 令牌名称
*/
export const AXIOS_TOKEN_NAME = 'Authorization'
/**
* 令牌名称
*/
export const AXIOS_REFRESH_TOKEN_NAME = 'refresh_token'
/**
* 令牌刷新时间差 单位:分组
*/
export const AXIOS_REFRESH_DIFFERENCE_MINUTES = 1
/**
* 令牌头前缀
*/
export const AXIOS_TOKEN_CONTENT_PREFIX = 'Bearer '
/**
* 失败重试次数
*/
export const AXIOS_RETRIES = 3
/**
* 失败重试时间间隔
*/
export const AXIOS_DELAY_TIME = 1500
/**
* 异常code
*/
export const exceptionCode = []
/**
* 异常白名单
*/
export const exceptionWhitelist = (response: AxiosResponse) => response.data?.status
里面设置了一些常用的涉及请求的常量,这里不一一介绍了的。
在index.ts
文件中可以看到const axiosPro = axiosProCreator(fetch)
,那么我们就来看下的这个axiosProCreator
是啥
//axiosProCreator
/**
* @ Description: axios扩展插件
*/
import type { AxiosInstance, AxiosStatic } from 'axios'
import type { AxiosPlugin } from '../interface'
declare module 'axios' {
interface Axios {
use(axiosPlugin: AxiosPlugin, options?: any): void
}
}
export default function axiosProCreator(axios: AxiosStatic | AxiosInstance) {
axios.use = function (axiosPlugin: AxiosPlugin, options?: any) {
axiosPlugin.install(axios, options)
}
return axios
}
分析:
ts
中的declare module
意为声明模块。www.bilibili.com/video/BV179…axiosProCreator
函数中,为传递进来的axios
参数添加了use
方法,内部调用了install
方法,然后返回参数axios
interface.ts
接口类型文件:
import type { AxiosInstance, AxiosStatic } from 'axios'
export interface AxiosPlugin {
install: (axios: AxiosStatic | AxiosInstance, options: any) => void
}
export interface AxiosPluginInstance {
install(axios: AxiosStatic | AxiosInstance, options: any): void
}
分析:
- 定义了接口类型,接口
AxiosPlugin
和AxiosPluginInstance
都要有一个install
方法
接下来看下第一个插件的axiosExceptionPlugin
--异常插件:
/**
* @ Description: 异常插件
*/
import type { AxiosPlugin } from '../interface'
import type { AxiosInstance, AxiosResponse, AxiosStatic } from 'axios'
import isArray from 'lodash/fp/isArray'
import isFunction from 'lodash/fp/isFunction'
interface Options {
/**
* 异常code
*/
exceptionCode: string[]
/**
* 白名单
*/
whitelist: string[] | ((response: AxiosResponse<any, any>) => Boolean)
}
export const axiosExceptionPlugin: AxiosPlugin = {
install: (axios: AxiosStatic | AxiosInstance, options: Options) => {
axios.interceptors.response.use(
(response) => {
// 2xx 范围内的状态码都会触发该函数。
const code = response.data?.code
const whitelist = options.whitelist
if (
/**
* 数组,code在白名单中
*/
(isArray(whitelist) && whitelist.includes(code)) ||
/**
* 函数返回值为真
*/
(isFunction(whitelist) && whitelist(response))
) {
return response
}
return Promise.reject(response.data)
},
function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error)
}
)
}
}
分析:
axiosExceptionPlugin
插件中有一个install
方法,通过axios.interceptors.response
做了一层响应拦截,响应码状态码在2xx
范围内,或者在白名单中的话,就返回数据response
。
然后是重试插件-axiosRetryPlugin
:
//
/**
* @ name: axiosRetryPlugin
* @ Description: 错误重试插件
*/
import { AxiosError, type AxiosInstance, type AxiosStatic } from 'axios'
import type { AxiosPlugin } from '../interface'
interface Options {
retries: number // 重试次数
delayTime: number // 每次重试之间的实践间隔为 retry count * delayTime
}
declare module 'axios' {
interface AxiosRequestConfig {
retryIndex?: number
}
}
/**
* axios重试插件
* @param axios
* @returns
*/
export const axiosRetryPlugin: AxiosPlugin = {
install: (axios: AxiosStatic | AxiosInstance, options: Options) => {
axios.interceptors.response.use(undefined, function (error) {
if (error instanceof AxiosError) {
const { config } = error
if (config?.retryIndex === undefined) config.retryIndex = 1
if (config.retryIndex <= options.retries) {
return new Promise((resolve) => {
setTimeout(() => {
if (config.retryIndex) config.retryIndex++
return resolve(axios(config))
}, options.delayTime * (config.retryIndex || 1))
})
}
return Promise.reject(error)
}
return Promise.reject(error)
})
return axios
}
}
接着继续看下令牌插件-axiosTokenPlugin
:
/**
* @ name:axiosTokenPlugin
* @ Description: 令牌插件
*/
import { AXIOS_TOKEN_CONTENT_PREFIX } from '@/config/axios.config'
import type { AxiosInstance, AxiosStatic } from 'axios'
import type { AxiosPlugin } from '../interface'
/**
* axios token挂载插件
* @param axios
* @returns
*/
interface Options {
tokenName: string // 令牌名称
tokenResource: () => any // token资源
}
export const axiosTokenPlugin: AxiosPlugin = {
install: (axios: AxiosStatic | AxiosInstance, options: Options) => {
axios.interceptors.request.use(async (config) => {
const token = await options.tokenResource()
if (config && config.headers && token)
config.headers[options.tokenName] = `${AXIOS_TOKEN_CONTENT_PREFIX}${token}`
return config
})
}
}
path替换插件-axiosPathToRegexp
:
/**
* @ Name: axiosPathToRegexp
* @ Description: 路径替换插件
*/
import type { AxiosPlugin } from '../interface'
import type { AxiosInstance, AxiosStatic } from 'axios'
import { compile } from 'path-to-regexp'
declare module 'axios' {
interface AxiosRequestConfig {
pathParams?: Record<string, any>
}
}
export const axiosPathToRegexp: AxiosPlugin = {
install: (axios: AxiosStatic | AxiosInstance) => {
axios.interceptors.request.use((config) => {
if (config.pathParams && config.url) {
config.url = compile(config.url)(config.pathParams)
}
return config
})
}
}
请求防抖插件-AxiosDebouncePlugin :
/**
* @ Name: AxiosDebouncePlugin
* @ Description: 防抖插件
*/
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosStatic } from 'axios'
import type { AxiosPluginInstance } from '../interface'
declare module 'axios' {
interface Axios {
$get<T = any, R = AxiosResponse<T>, D = any>(
url: string,
config?: AxiosRequestConfig<D>
): Promise<R>
$post<T = any, R = AxiosResponse<T>, D = any>(
url: string,
data?: D,
config?: AxiosRequestConfig<D>
): Promise<R>
$delete<T = any, R = AxiosResponse<T>, D = any>(
url: string,
config?: AxiosRequestConfig<D>
): Promise<R>
$put<T = any, R = AxiosResponse<T>, D = any>(
url: string,
data?: D,
config?: AxiosRequestConfig<D>
): Promise<R>
$patch<T = any, R = AxiosResponse<T>, D = any>(
url: string,
data?: D,
config?: AxiosRequestConfig<D>
): Promise<R>
}
}
type Method = 'get' | 'post' | 'delete' | 'put' | 'patch'
export interface Options {
methods: Method[]
}
const DEFAULT_OPTIONS: Options = {
methods: ['get', 'post', 'delete', 'put', 'patch']
}
class AxiosDebouncePlugin implements AxiosPluginInstance {
/**
* 执行区
*/
private execution: Record<string, any> = {}
/**
* 默认配置
*/
private defaultOptions: Options
constructor(private options?: Options) {
this.defaultOptions = Object.assign({}, DEFAULT_OPTIONS, options)
}
/**
* 执行区增加新成员
* @param key
* @param calFun
*/
private pushExecution(key: string, calFun: Promise<any>) {
this.execution[key] = calFun
}
/**
* 移除执行区
* @param key
*/
private removeExecution(key: string) {
delete this.execution[key]
}
/**
* 是否正在执行
* @param key
* @returns
*/
isPending(key: string) {
return Reflect.has(this.execution, key)
}
/**
* key值生成器
* @param url
* @param data
* @returns
*/
keyCreator(url: string, data: Record<string, any>) {
return `${url}: ${JSON.stringify(data)}`
}
/**
* 执行拦截器
* @param target
* @param argArray
* @returns
*/
exeInterceptor(
target:
| (<T = any, R = AxiosResponse<T, any>, D = any>(
url: string,
config?: AxiosRequestConfig<D> | undefined
) => Promise<R>)
| (<T = any, R = AxiosResponse<T, any>, D = any>(
url: string,
data?: D,
config?: AxiosRequestConfig<D>
) => Promise<R>),
argArray: any[]
) {
const key = this.keyCreator(argArray[0], argArray[1])
if (this.isPending(key)) {
/**
* 正在执行中
*/
return this.execution[key]
} else {
/**
* 执行区中无此项实例正在执行
*/
const call = target(argArray[0], argArray[1], argArray[2]).finally(() =>
this.removeExecution(key)
)
this.pushExecution(key, call)
return call
}
}
/**
* 插件安装
* @param axios
*/
install(axios: AxiosStatic | AxiosInstance) {
this.defaultOptions.methods.forEach((method) => {
if (method === 'get' || method === 'delete') {
axios[`$${method}`] = new Proxy(axios[method], {
apply: (target, thisArg, argArray) => {
return this.exeInterceptor(target, argArray)
}
})
} else {
axios[`$${method}`] = new Proxy(axios[method], {
apply: (target, thisArg, argArray) => {
return this.exeInterceptor(target, argArray)
}
})
}
})
}
}
export default AxiosDebouncePlugin
控制器插件-axiosControllerPlugin
/**
* @ Name: axiosControllerPlugin
* @ Description: 控制器插件
*/
import type { AxiosInstance, AxiosStatic } from 'axios'
import type { AxiosPlugin } from '../interface'
declare module 'axios' {
interface AxiosRequestConfig {
controller?: AbortController
}
}
/**
* axios token挂载插件
* @param axios
* @returns
*/
export const axiosControllerPlugin: AxiosPlugin = {
install: (axios: AxiosStatic | AxiosInstance) => {
axios.interceptors.request.use(async (config) => {
config.signal = config.data?.$controller || config.params?.$controller
delete config.data?.$controller
delete config.params?.$controller
return config
})
}
}
主要就是弄明白弄清楚这种插拔式的拆分思路吧,理解和沉淀都需要时间,看完不是重点,弄会理解才是。
转载自:https://juejin.cn/post/7215035912186167355