# 前言

axios 目前用于处理前端项目里的 ajax 请求,每个项目里为了方便对 axios 进行操作,都会对 axios 进行封装,本文主要结合之前项目实践以 vue3 和 typescript 对 axios 完成统一封装,做到一个开箱即用的 axios。封装后具有以下好处:

  1. 使用时代码提示功能更丰富;
  2. 灵活的拦截器;
  3. 支持请求重试功能,以及自定义请求重试次数;
  4. 支持扩展自定义请求头配置。

# 安装依赖

首先需要先安装依赖

1
npm i axios -S

# 基础封装 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 {
// axios实例
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
// request 拦截器
this.instance.interceptors.request.use(
(config: AxiosRequestConfig) => {
return config
},
(error: AxiosError) => {
return Promise.reject(error)
}
)
// response 拦截器
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)
// 设置变量追踪__retryCount的值
config.__retryCount = config.__retryCount || 0
// 判断是否超出最大重试次数
if (config.__retryCount >= config.retry) {
return Promise.reject(error)
}
config.__retryCount += 1
// Create new promise to handle exponential backoff
let backoff = new Promise(function (resolve) {
setTimeout(function () {
resolve()
}, config.retryDelay || 1)
})
// Return the promise in which recalls axios to retry the request
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'	
// 定义get方法
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);
}
}
// 定义post方法
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 {
// axios 实例
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
// request 拦截器
this.instance.interceptors.request.use(
(config: AxiosRequestConfig) => {
return config
},
(error: AxiosError) => {
return Promise.reject(error)
}
)
// response 拦截器
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)
// 设置变量追踪__retryCount的值
config.__retryCount = config.__retryCount || 0
// 判断是否超出最大重试次数
if (config.__retryCount >= config.retry) {
return Promise.reject(error)
}
config.__retryCount += 1
// Create new promise to handle exponential backoff
let backoff = new Promise(function (resolve) {
setTimeout(function () {
resolve()
}, config.retryDelay || 1)
})
// Return the promise in which recalls axios to retry the request
return backoff.then(function () {
return service(config)
})
}
)
}
// 定义get方法
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);
}
}
// 定义post方法
public post<T = any>(
url: string,
data?: any,
config?: AxiosRequestConfig
): Promise<BizResponse<T>> {
return this.instance.post(url, data, config);
}
}

export default new Request({})