目录
  1. 1. 为什么需要封装
  2. 2. Axios 封装思路
  3. 3. 封装
    1. 3.1. 创建类型
    2. 3.2. 封装 Http 类
  4. 4. 使用
    1. 4.1. 创建多实例
    2. 4.2. 单个请求拦截器
    3. 4.3. 请求数据类型限制
  5. 5. Vue3 中应用
    1. 5.1. 配置
    2. 5.2. 实例化
    3. 5.3. 接口
    4. 5.4. 使用接口
TS 封装 Axios 及 Vue3 中的使用

为什么需要封装

无论使用了第三方库或工具时,我们都将有可能面临该库功能不足以满足项目需求,或该库已经停止维护等需要更换库的情况。封装能更好的维护使用的库,更换也更加方便。Axios 作为一个网络请求库,将贯穿整个项目,封装更是必要的。

Axios

Axios 封装思路

需求:

  • 多实例,实例间配置独立(项目可能多个请求地址或独立的实例拦截器)
  • 请求方法封装
  • 可对某个请求进行请求拦截
  • 请求数据类型限制

这里将封装成一个类,并进行类型限制,利用 Axios 内置 axios.create 创建实例,并对请求方法封装。


封装

创建类型

  • AxiosRequestConfig: Axios 请求配置类型;
  • AxiosResponse: Axios 响应类型;
  • HttpConfig: 新建自定义请求配置类型,添加请求拦截对象,应用在单请求拦截;
  • HttpInterceptors: 新建拦截对象类型,包含请求和响应拦截;
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
import type { AxiosRequestConfig, AxiosResponse } from 'axios'

/**
* @description 拦截器类型
* @author VincentF0ng
* @export
* @interface HttpInterceptors
*/
export interface HttpInterceptors {
requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig
requestInterceptorCatch?: (err: any) => any
responseInterceptor?: (res: AxiosResponse) => AxiosResponse
responseInterceptorCatch?: (err: any) => any
}

/**
* @description 请求配置类型,继承自 Axios 配置类型
* @author VincentF0ng
* @export
* @interface HttpConfig
* @extends {AxiosRequestConfig}
*/
export interface HttpConfig extends AxiosRequestConfig {
interceptors?: HttpInterceptors
}

封装 Http 类

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
import axios from 'axios'
import type { AxiosInstance, AxiosResponse } from 'axios'
import type { HttpConfig, HttpInterceptors } from './types'

/**
* @description Axios封装
* @author VincentF0ng
* @export
* @class Http
*/
export default class Http {
instance: AxiosInstance
interceptors?: HttpInterceptors

constructor(config: HttpConfig) {
this.instance = axios.create(config)
this.interceptors = config.interceptors

// 实例请求拦截器
this.instance.interceptors.request.use(
this.interceptors?.requestInterceptor,
this.interceptors?.requestInterceptorCatch
)

// 实例响应拦截器
this.instance.interceptors.response.use(
this.interceptors?.responseInterceptor,
this.interceptors?.responseInterceptorCatch
)
}

/**
* @description 封装请求函数,实现单个请求的拦截器
* @param {HttpConfig} config
* @memberof Http
*/
request(config: HttpConfig): Promise<AxiosResponse> {
// 单个请求拦成功截器
if (config.interceptors?.requestInterceptor) {
config = config.interceptors.requestInterceptor(config)
}
return new Promise((resolve, reject) => {
this.instance
.request(config)
.then(res => {
// 单个响应拦成功截器
if (config.interceptors?.responseInterceptor) {
res = config.interceptors.responseInterceptor(res)
}
return resolve(res)
})
.catch(err => {
// 单个响应拦失败截器
if (config.interceptors?.responseInterceptorCatch) {
err = config.interceptors.responseInterceptorCatch(err)
}
return reject(err)
})
})
}

// 方法封装重置
get<T = any>(url: string, params?: T, config?: HttpConfig): Promise<any> {
return this.request({ ...config, url, method: 'GET', params })
}

post<T = any>(url: string, data: T, config?: HttpConfig): Promise<any> {
return this.request({ ...config, url, method: 'POST', data })
}

put<T = any>(url: string, data: T, config?: HttpConfig): Promise<any> {
return this.request({ ...config, url, method: 'PUT', data })
}

delete<T = any>(url: string, data: T, config?: HttpConfig): Promise<any> {
return this.request({ ...config, url, method: 'DELETE', data })
}

options<T = any>(url: string, params?: T, config?: HttpConfig): Promise<any> {
return this.request({ ...config, url, method: 'OPTIONS', params })
}
}

使用

创建多实例

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
import Http from '@/utils/request'

// 创建实例
const http1 = new Http({
baseURL: '/api',
interceptors: {
requestInterceptor: config => {
// 实例请求拦截器
return config
},
responseInterceptor: response => {
// 实例响应成功拦截
return response
},
responseInterceptorCatch: err => {
// 实例响应失败拦截器
return err
}
}
})
// 创建多实例
const http2 = new Http({ baseURL: '/api-test' })


// 使用
http1.get('/login').then(res => {
console.log(res)
})
http2.post('/user', { username: 'xxx', password: 'xxx' }).then(res => {
console.log(res)
})

单个请求拦截器

额外配置为第三参数

1
2
3
4
5
6
7
8
9
http1.post('/user', { 
username: 'xxx',
password: 'xxx'
}, {
requestInterceptor: config => {
// 请求拦截器
return config
}
})

请求数据类型限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 类型限制
interface IUserInfo {
username: string
password: string
age?: number
remark?: string
}

const userInfo = {
username: 'Vincent',
password: 'xxx'
}

http1.post('/user', userInfo: IUserInfo).then(res => {})


Vue3 中应用

1
2
3
4
5
6
7
8
9
10
├── src
│ ├── utils
│ │ └── request # 封装Axios
│ │ ├── index.ts
│ │ └── types.ts
│ └── http # 接口相关
│ ├── api.ts # 接口
│ ├── types.ts # 接口数据类型
│ ├── config.ts # 配置
│ └── index.ts # 实例化

配置

src/http/config.ts

1
2
3
4
5
6
7
8
9
10
11
12
let BASE_URL = ''
const TIME_OUT = 5000

if (process.env.NODE_ENV === 'development') {
BASE_URL = './api-dev'
} else if (process.env.NODE_ENV === 'production') {
BASE_URL = '/api'
} else {
BASE_URL = './mapi'
}

export { BASE_URL, TIME_OUT }

实例化

src/http/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
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 Http from '@/utils/request'
import { BASE_URL, TIME_OUT } from './config'
import { ElMessage } from 'element-plus'

export const http = new Http({
baseURL: BASE_URL,
timeout: TIME_OUT,
withCredentials: true,
interceptors: {
// 请求拦截,添加 token
requestInterceptor: config => {
if (config.url !== '/login' && window.sessionStorage.token) {
config.headers.Authorization = window.sessionStorage.token
// ...
// 其他配置
}
return config
},
// 响应成功拦截,处理数据
responseInterceptor: response => {
const code = response.status
const res = response.data

switch (true) {
case code >= 200 && code < 300:
return res
// case code === 401:
// router.push({ path: '/' })
// ElMessage.error('请先登录')
// return Promise.reject(null)
// ...
// 其他拦截
default:
return res
}
},
// 响应失败拦截,错误处理
responseInterceptorCatch: err => {
if (err.response) {
// ...
// 错误处理
} else {
ElMessage.error('请求无响应')
return Promise.reject(err)
}
}
}
})

接口

接口数据类型限制
src/http/types.ts

1
2
3
4
5
6
7
8
interface IUserInfo {
username: string
password: string
age?: number
remark?: string
}

export { IUserInfo }

接口封装
src/http/api.ts

1
2
3
4
5
6
7
8
9
10
11
12
import { http } from './index'
import { IUserInfo } from './types'

// 登录 - 获取token
export const getToken = () => {
return http.get('/login')
}

// 用户管理 - 添加账户
export const postAddUser = (data: IUserInfo) => {
return http.post('/user', data)
}

使用接口

1
2
3
4
5
6
7
8
9
import { postAddUser } from '@/http/api'

postAddUser({
username: 'xxx',
password: 'xxx',
// ...
}).then((res) => {
// 请求成功处理
})

文章作者: Vincent F0ng
文章链接: https://vincef0ng.cn/post/encapsulate-axios-with-ts/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Vincent F0ng

评论(支持Markdown)