为什么需要封装
无论使用了第三方库或工具时,我们都将有可能面临该库功能不足以满足项目需求,或该库已经停止维护等需要更换库的情况。封装能更好的维护使用的库,更换也更加方便。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'
export interface HttpInterceptors { requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig requestInterceptorCatch?: (err: any) => any responseInterceptor?: (res: AxiosResponse) => AxiosResponse responseInterceptorCatch?: (err: any) => any }
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'
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 ) }
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 │ │ ├── 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: { 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 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'
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) => { })
|