axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
特性
- 从浏览器中创建 XMLHttpRequests
- 从 node.js 创建 http 请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF
封装的目的
简化请求的配置
统一处理请求和响应
处理特殊业务逻辑场景
提高维护性,减少代码量
封装实现功能
全局拦截
实例拦截
单个请求拦截
控制全局loaing
控制全局消息提示
取消接口重复请求
目录结构

types.ts 类型文件
ts
/*
* @Author: vhen
* @Date: 2024-01-20 23:25:05
* @LastEditTime: 2024-01-23 17:26:09
* @Description: 现在的努力是为了小时候吹过的牛逼!
* @FilePath: \react-vhen-blog-admin\src\http\errorHandler.ts
*
*/
export const HttpStatus = (status: number) => {
let message: string = "";
switch (status) {
case 400:
message = "请求错误(400)";
break;
case 401:
message = "token失效,请重新登录(401)";
break;
case 403:
message = "拒绝访问(403)";
break;
case 404:
message = "请求出错(404)";
break;
case 406:
message = "请求的格式不可得(406)";
break;
case 408:
message = "请求超时(408)";
break;
case 410:
message = "请求的资源被永久删除,且不会再得到的(410)";
break;
case 500:
message = "服务器故障(500)";
break;
case 501:
message = "服务未实现(501)";
break;
case 502:
message = "网络错误(502)";
break;
case 503:
message = "服务不可用,服务器暂时过载或维护(503)";
break;
case 504:
message = "网络超时(504)";
break;
case 505:
message = "HTTP版本不受支持(505)";
break;
default:
message = `服务器异常,请联系管理员!`;
}
return message;
}
httpRequest.tsx 网络请求封装文件
tsx
/*
* @Author: vhen
* @Date: 2024-01-20 18:34:37
* @LastEditTime: 2024-01-23 02:37:07
* @Description: 现在的努力是为了小时候吹过的牛逼!
* @FilePath: \react-vhen-blog-admin\src\http\httpRequest.tsx
*
*/
import { Spin, notification } from 'antd';
import axios, { AxiosError, AxiosInstance, AxiosResponse, Canceler } from 'axios';
import ReactDOM from 'react-dom/client';
import { HttpStatus } from './errorHandler';
import type { IResult, RequestConfig, RequestInterceptors } from './types';
class HttpRequest {
// axios 实例
private instance: AxiosInstance
// 拦截器对象
private interceptors?: RequestInterceptors<AxiosResponse>
// 存储请求配置
private requestConfig: RequestConfig = {
isShowMessage: true,
isShowSuccessMessage: false,
isShowErrorMessage: true,
cancelRequest: true,
isShowLoading: true,
}
// 处理loading
private requestCount: number = 0
// 存放取消请求控制器Map
private abortControllerMap: Map<string, AbortController | Canceler> = new Map()
constructor(config: RequestConfig) {
this.requestConfig = Object.assign(this.requestConfig, config)
// axios.create创建axios实例,接收传递进来的配置
this.instance = axios.create(config)
this.interceptors = config?.interceptors
this.setupInterceptors()
}
/**
* 初始化拦截器
*/
private setupInterceptors() {
// 给每一个instance实例都添加拦截器
this.instance.interceptors.request.use(
(config: any) => {
this.requestConfig = Object.assign(this.requestConfig, config)
this.showLoading()
// 如果有重复的,在这里就去除掉
this.removePending(config as RequestConfig)
// 添加下次的判定
this.addPending(config as RequestConfig)
// 一般会请求拦截里面加token,用于后端的验证
const token = localStorage.getItem("token") as string
if (token) {
config.headers!.Authorization = `Bearer ${token}`;
}
console.log('全局请求成功的拦截')
return config
},
(error: AxiosError) => {
this.hideLoading()
return Promise.reject(error)
},
)
this.instance.interceptors.response.use(
(response) => {
this.hideLoading()
const { data, status } = response
console.log('全局响应成功的拦截')
this.removePending(response.config as RequestConfig)
if (status === 200) {
return data
}
if (this.requestConfig.isShowMessage && this.requestConfig.isShowErrorMessage) {
notification.error({
message: '温馨提示',
description: HttpStatus(status),
})
}
return HttpStatus(status)
},
(error) => {
this.hideLoading()
return Promise.reject(error);
},
)
// 针对特定的HttpRequest实例,比如HttpRequest2,添加拦截器
this.instance.interceptors.request.use(
this.interceptors?.requestInterceptors,
this.interceptors?.requestInterceptorsCatch,
)
this.instance.interceptors.response.use(
this.interceptors?.responseInterceptors,
this.interceptors?.responseInterceptorsCatch,
)
}
/**
* 生成每个请求唯一的键
* @param config
* @returns
*/
private getPendingKey(config: RequestConfig): string {
let { data } = config
const { url, method, params } = config
if (typeof data === 'string') data = JSON.parse(data)
return [url, method, JSON.stringify(params), JSON.stringify(data)].join('&')
}
/**
* 储存每个请求唯一值,用于取消请求
* @param config
*/
private addPending(config: RequestConfig) {
if (this.requestConfig?.cancelRequest) {
const pendingKey = this.getPendingKey(config)
config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
if (!this.abortControllerMap.has(pendingKey))
this.abortControllerMap.set(pendingKey, cancel)
})
}
}
/**
* 删除重复的请求
* @param config
*/
private removePending(config: RequestConfig) {
if (this.requestConfig?.cancelRequest) {
const pendingKey = this.getPendingKey(config)
if (this.abortControllerMap.has(pendingKey)) {
const abortController = this.abortControllerMap.get(pendingKey)
if (abortController instanceof AbortController) {
abortController.abort()
} else {
abortController!(pendingKey)
}
this.abortControllerMap.delete(pendingKey)
}
}
}
/**
* 显示loading
*/
private showLoading() {
if (this.requestConfig.isShowLoading && this.requestCount < 1) {
const dom = document.createElement('div')
dom.setAttribute('id', 'r-loading')
dom.style.display = "flex"
dom.style.alignItems = 'center'
dom.style.justifyContent = 'center'
dom.style.width = "100%"
dom.style.height = "100%"
if (this.requestConfig?.loadDom) {
(document.getElementById(this.requestConfig.loadDom) as HTMLElement).appendChild(dom)
} else {
document.body.appendChild(dom)
}
ReactDOM.createRoot(dom).render(<Spin size="large" />)
}
this.requestCount++
}
/**
* 隐藏loading
*/
private hideLoading() {
if (this.requestConfig.isShowLoading) {
this.requestCount--
if (this.requestCount === 0) {
if (this.requestConfig.loadDom) {
(document.getElementById(this.requestConfig.loadDom) as HTMLElement).removeChild(document.getElementById('r-loading') as HTMLElement)
} else {
document.body.removeChild(document.getElementById('r-loading') as HTMLElement)
}
}
}
}
public request<T = any>(config: RequestConfig<T>): Promise<IResult<T>> {
if (config?.url) {
// 外层带https或http开头的url,不需要拼接baseURL,否则就读取实例中 baseUR
const isHttp = config.url.indexOf('http') !== -1
config.url = isHttp ? config.url : this.requestConfig.baseURL + config.url
}
return new Promise<IResult<T>>((resolve, reject) => {
// 单个请求设置拦截器
if (config.interceptors?.requestInterceptors) {
this.instance.interceptors.request.use(config.interceptors.requestInterceptors)
}
this.instance
.request<any, T>(config)
.then(res => {
// 如果我们为单个响应设置拦截器,这里使用单个响应的拦截器
if (config.interceptors?.responseInterceptors) {
res = config.interceptors.responseInterceptors(res)
}
resolve(res as IResult<T>)
})
.catch((error: any) => {
reject(error)
})
})
}
// GET类型的网络请求
public get<T = any>(url: string, config?: RequestConfig): Promise<AxiosResponse<IResult<T>>> {
return this.instance.get(url, config)
}
// POST类型的网络请求
public post<T = any>(url: string, data?: any, config?: RequestConfig): Promise<AxiosResponse<IResult<T>>> {
return this.instance.post(url, data, config);
}
// PUT类型的网络请求
public put<T = any>(url: string, data?: any, config?: RequestConfig): Promise<AxiosResponse<IResult<T>>> {
return this.instance.put(url, data, config);
}
// DELETE类型的网络请求
public delete<T = any>(url: string, config?: RequestConfig): Promise<AxiosResponse<IResult<T>>> {
return this.instance.delete(url, config);
}
}
export default HttpRequest;
index.ts 请求配置文件
ts
/*
* @Author: vhen
* @Date: 2024-01-20 18:34:37
* @LastEditTime: 2024-01-23 17:33:34
* @Description: 现在的努力是为了小时候吹过的牛逼!
* @FilePath: \react-vhen-blog-admin\src\http\index.ts
*
*/
import HttpRequest from "./httpRequest";
// 配置1
export const http1 = new HttpRequest({
baseURL: 'https://api.vvhan.com/api',
cancelRequest: true,
interceptors: {
requestInterceptors: config => {
console.log('实例请求拦截器')
return config
},
// 响应拦截器
responseInterceptors: result => {
console.log('实例响应拦截器')
return result
},
}
})
// 配置2
export const http2 = new HttpRequest({
baseURL: "https://jsonplaceholder.typicode.com",
interceptors: {
requestInterceptors: config => {
console.log('实例请求拦截器')
return config
},
// 响应拦截器
responseInterceptors: result => {
console.log('实例响应拦截器')
return result
},
}
})
- 最终实现

测试
- 测试一
ts
import { http1, http2 } from '@/http';
const getHistoryList = () => {
http1.request({
url: '/hotlist?type=history',
method: 'post',
loadDom: 'box',
interceptors: {
requestInterceptors(res) {
console.log('单接口请求成功的拦截')
return res
},
requestInterceptorsCatch(error) {
console.log('单接口请求失败的拦截');
return error
},
responseInterceptors(result) {
console.log('单接口响应成功的拦截')
return result
},
responseInterceptorsCatch(error) {
console.log('单接口响应失败的拦截');
return error
}
},
}).then(res => {
console.log('请求结果', res);
})
}
useEffect(() => {
getHistoryList()
}, [])

- 测试二
ts
useEffect(() => {
getHistoryList()
getHistoryList()
}, [])

- 测试三(局部挂载请求loading)


- 测试四
ts
http1.get('/hotlist?type=history', {
loadDom: 'box',
})
// http1.post('/hotlist?type=history', {
// loadDom: 'box',
// })
github
项目地址:nest_vhen_blog
结束语
本人菜鸟一枚,笔勤能使手快,多练能使手巧;如有不足之处,望海涵,欢迎关注一起学习,一起成长