# 前言
axios 目前用于处理前端项目里的 ajax 请求,每个项目里为了方便对 axios 进行操作,都会对 axios 进行封装,本文主要结合之前项目实践以 vue3 和 typescript 对 axios 完成统一封装,做到一个开箱即用的 axios。封装后具有以下好处:
- 使用时代码提示功能更丰富;
- 灵活的拦截器;
- 支持请求重试功能,以及自定义请求重试次数;
- 支持扩展自定义请求头配置。
# 安装依赖
首先需要先安装依赖
# 基础封装 request 类
新建 index.ts,首先实现一个最基础的版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import axios from 'axios' import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from "axios"
class Request { instance: AxiosInstance baseConfig: AxiosRequestConfig = { baseURL: window._env_.baseURL + window._env_.URL_PREFIX, timeout: 2000, headers: { "Content-Type": "application/json", "X-GW-AccessKey": window._env_.accessKey, } } constructor(config: AxiosRequestConfig) { this.instance = axios.create(Object.assign(this.baseConfig, config)) } request(config: AxiosRequestConfig) { return this.instance.request(config) } } export default Request
|
注意: constructor
函数中的参数 config
是在 api.ts 中请求接口时自定义的一些配置,比如单独设置请求头里的 responseType: 'arraybuffer'
,通过合并基础配置与自定义配置实现请求头里的灵活配置
这里将其封装为一个类,而不是一个函数的原因是因为类可以创建多个实例,适用范围更广,封装性更强一些。
# 封装拦截器
拦截器主要包括请求拦截器和响应拦截器,请求拦截器可以在请求时添加 token,响应拦截器可以根据错误码自定义处理等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| import { ElMsgToast } from "@enn/ency-design" import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from "axios" constructor(config: AxiosRequestConfig) { this.instance = axios.create(Object.assign(this.baseConfig, config)) this.instance.defaults.retry = 1 this.instance.defaults.retryDelay = 500 this.instance.interceptors.request.use( (config: AxiosRequestConfig) => { return config }, (error: AxiosError) => { return Promise.reject(error) } ) this.instance.interceptors.response.use( (response: AxiosResponse) => { if (response?.data?.success == false) { ElMsgToast.error(response?.data.message) } return response.data; }, (error: AxiosError) => { let config = error.config if (!config || !config.retry) return Promise.reject(error) config.__retryCount = config.__retryCount || 0 if (config.__retryCount >= config.retry) { return Promise.reject(error) } config.__retryCount += 1 let backoff = new Promise(function (resolve) { setTimeout(function () { resolve() }, config.retryDelay || 1) }) return backoff.then(function () { return service(config) }) } ) }
|
在遇到网络问题导致请求失败时,会根据设置的请求重试次数以及请求间隔时间来处理请求重试,优化请求体验
# 封装请求方法
这里我们只对常用的 get 和 post 请求做下封装,put 和 delete 请求可同理参考。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import qs from 'qs'
public get<T = any>( url: string, data?: any, config?: AxiosRequestConfig ): Promise<BizResponse<T>> { if (data && Object.keys(data).length) { return this.instance.get(`${url}?${qs.stringify(data, {indices: false})}`, config); } else { return this.instance.get(url, config); } }
public post<T = any>( url: string, data?: any, config?: AxiosRequestConfig ): Promise<BizResponse<T>> { return this.instance.post(url, data, config); }
|
此处 get 请求对参数 data 做了下判断,如果是对象形式,则将参数序列化。
BizResponse
是通用接口返回结构,一般定义方式为:
1 2 3 4 5 6
| export interface BizResponse<T = Record<string, unknown> | Array<unknown>> { code: string; message: string; data: T & { pageNum?: number; pageSize?: number; total?: number }; success: boolean; }
|
一般到此就完成了 axios 的封装,完整代码附在文章最后
封装完成后,如何使用呢,一般我们在 api.ts 中对接口会做统一的管理,例如:
1 2 3 4 5
| import Request from '@/axios' import { BizResponse } from '@/types' export const exportAccidentPrevent: (data: string) => Promise<BizResponse> = (data: string) => { return request.get('/api/exportAccidentPrecautions', data, {responseType: 'arraybuffer'}) }
|
在业务组件中调用该 api 时如下:
1 2 3 4 5
| <script setup lang="ts"> onMounted(async () => { const res = await exportAccidentPrevent('accExport') }) </script>
|
完整封装 axios 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| import axios from "axios" import qs from 'qs' import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from "axios" import { ElMsgToast } from "@enn/ency-design"
interface BizResponse<T = Record<string, unknown> | Array<unknown>> { code: string; message: string; data: T & { pageNum?: number; pageSize?: number; total?: number }; success: boolean; }
class Request { instance: AxiosInstance baseConfig: AxiosRequestConfig = { baseURL: window._env_.baseURL + window._env_.URL_PREFIX, timeout: 10000, headers: { "Content-Type": "application/json", "X-GW-AccessKey": window._env_.accessKey, } } constructor(config: AxiosRequestConfig) { this.instance = axios.create(Object.assign(this.baseConfig, config)) this.instance.defaults.retry = 1 this.instance.defaults.retryDelay = 500 this.instance.interceptors.request.use( (config: AxiosRequestConfig) => { return config }, (error: AxiosError) => { return Promise.reject(error) } ) this.instance.interceptors.response.use( (response: AxiosResponse) => { if (response?.data?.success == false) { ElMsgToast.error(response?.data.message) } return response.data; }, (error: AxiosError) => { let config = error.config if (!config || !config.retry) return Promise.reject(error) config.__retryCount = config.__retryCount || 0 if (config.__retryCount >= config.retry) { return Promise.reject(error) } config.__retryCount += 1 let backoff = new Promise(function (resolve) { setTimeout(function () { resolve() }, config.retryDelay || 1) }) return backoff.then(function () { return service(config) }) } ) } public get<T = any>( url: string, data?: any, config?: AxiosRequestConfig ): Promise<BizResponse<T>> { if (data && Object.keys(data).length) { return this.instance.get(`${url}?${qs.stringify(data, {indices: false})}`, config); } else { return this.instance.get(url, config); } } public post<T = any>( url: string, data?: any, config?: AxiosRequestConfig ): Promise<BizResponse<T>> { return this.instance.post(url, data, config); } }
export default new Request({})
|