欢迎加入本专栏!本专栏将引领您快速上手React,让我们一起放弃放弃的念头,开始学习之旅吧!我们将从搭建React项目开始,逐步深入讲解最核心的hooks,以及React路由、请求、组件封装以及UI(Ant Design)框架的使用。让我们一起掌握React,开启前端开发的全新篇章!
axios是一个基于Promise的HTTP网络请求库,可以用于浏览器和node.js。在服务端它使用原生node.js http模块, 而在客户端 (浏览端) 则使用XMLHttpRequest。它可以帮助我们更轻松、简单地发出 AJAX 请求。
1、它支持Promise API,能够处理异步请求,降低了回调地狱的问题。
2、它支持取消请求,可以在请求未完成时取消请求,避免浪费资源。
3、高开发效率,简化代码逻辑。
4、它提供了拦截器可以拦截请求和响应,可以在发送请求或接收响应之前对它们进行拦截和修改,从而实现统一处理或添加公共参数等功能。比如我们要做的同一接口防重复提交功能。
npm install axios --save
由于我之前写过一篇Vue3的,内容呢其实一摸一样的,无非就是里面使用的element框架的message提示替换一下,其次就是环境变量自己定义一下就好,我这里就直接贴源码,要看详细说明的朋友请到Vite + Vue3 封装 Axios 并做防重复提交 同一接口 [请求未返回结果、2秒内禁止重复提交](超详细)_vite封装axios-CSDN博客
为什么还要封装axios,因为它默认情况下不知道我们的业务需求,所以封装它最大的目的是适应项目需求。其次封装它可以将重复的代码抽象成一个方法,减少代码量,提高代码的可读性和可维护性以及复用性。统一处理响应错误、全局loading、设置请求头、请求超时处理等。
代码中基本上该注释的地方我都有写注释,因为我这里是demo,有些代码我并没有去实现。比如判断用户有没有登录,将X-Access-Token添加到每个请求等,都会在相应的位置写上注释,自己根据自己项目的需求去完成就好。
import axios, {
AxiosError,
AxiosInstance,
AxiosResponse,
Canceler,
InternalAxiosRequestConfig,
} from 'axios';
import { errorCodeType } from './error-code-type';
export class Interceptors {
requestQueue: {
createTime: number;
url: string;
method: string;
cancel: Canceler;
}[] = [];
instance: AxiosInstance;
constructor() {
this.instance = axios.create({
// 你的api地址 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
// 这里的话我的baseURL是存放在项目环境变量文件中
// vite获取env环境变量的方式是import.meta.env
baseURL: 'https://api.oioweb.cn/',
// 请求超时的毫秒数(0 表示无超时时间)
timeout: 1000,
});
this.init();
}
init() {
// 添加请求拦截器
this.instance.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
// 在这里的话你就可以去设置自己的请求头
// 比如用户登录以后就能拿到一个token 这个token比如格式是data: {token: '*****'}
// if (data.token) {
// config.headers['Authorization'] = `Bearer ${data.token}`
// config.headers['X-Access-Token'] = data.token
// }
// 防止一个接口在没有响应之前进行重新提交即重复提交验证,默认不校验 duplicateRequestValidation为true时开启
if (config.url && config.duplicateRequestValidation) {
this.removeRequestQueue(config);
this.addRequestQueue(config);
}
return config;
},
(error) => {
// 对请求错误做些什么 直接抛出错误
Promise.reject(error);
}
);
// 添加响应拦截器
this.instance.interceptors.response.use(
(response: AxiosResponse) => {
// 在这里的话你就可以去处理你响应成功的自定义逻辑
// 根据后端返回的code值。比如约定的是20000代表登录过期
// const res: any = response.data // 获取响应值
// if (res.code === 20000) {
// // 清楚token 跳转登录页面
// }
// 比如10000表示请求成功,约定40000~50000不做拦截
// const filterCode = Math.abs(parseInt(res.code)) >= 40000 && Math.abs(parseInt(res.code)) < 50000
// if (res.code !== 10000 && !filterCode) {
// // 这里去处理请求失败的逻辑
// } else {
// return response.data
// }
this.removeRequestQueue(response.config);
return response.data;
},
(error: AxiosError) => {
// 对响应错误做点什么
// 一般响应错误后端都会返回一个错误码和错误信息比如404 401等等
// 为了让用户更能直观的知道是什么原因 你可以把常见的错误做一个转换然后提示一下 404就是访问资源不存在,401就是没有权限等等
// 我演示的接口使用的是http://www.7timer.info/全球天气预测系统的接口
// 判断重复提交
// 转换错误编码为文字 进行提示让客户有更好的体验 超时要进行一个单独的处理
let message: string = error.message;
if (message.includes('Duplicate request')) {
console.log('-----------------禁止重复提交', message);
return Promise.reject(error);
} else if (message.includes('timeout of')) {
message = '系统接口请求超时';
this.removeOverTimeRequest();
} else if (error.response?.status) {
message = errorCodeType(error.response?.status);
}
console.log('-----------------', message);
return Promise.reject(error);
}
);
}
private addRequestQueue(config: InternalAxiosRequestConfig) {
// 如果是初次的话就直接push
if (this.requestQueue.length === 0) {
config.cancelToken = new axios.CancelToken((cancel: Canceler) => {
this.requestQueue.push({
url: config.url!,
method: config.method!,
cancel,
createTime: Date.now(),
});
});
} else {
// 这里做循环处理,如果正在请求中存在路径一样方法一样的情况,就直接取消请求
// 这里也可以根据自己的需求去扩展 比如参数不一样的话就通过等等
for (const [index, p] of Object.entries(this.requestQueue)) {
if (p.url === config.url && p.method === config.method) {
config.cancelToken = new axios.CancelToken((cancel: Canceler) => {
cancel('Duplicate request');
});
}
}
}
}
private removeRequestQueue(target: InternalAxiosRequestConfig) {
for (const [index, p] of Object.entries(this.requestQueue)) {
// 只有在指定的时间到了以后才会取消控制 继续请求 否则终止
if (
p.url === target.url &&
p.method === target.method &&
p.createTime &&
Date.now() - p.createTime > (target.duplicateRequestValidationTime || 0)
) {
p.cancel('Duplicate request');
this.requestQueue.splice(Number(index), 1);
}
}
}
private removeOverTimeRequest() {
const nowDate = Date.now();
for (const p in this.requestQueue) {
const { createTime } = this.requestQueue[p];
const time = nowDate - createTime;
if (time >= 10000) {
this.requestQueue.splice(Number(p), 1);
}
}
}
// 返回一下
getInterceptors() {
return this.instance;
}
}
import { AxiosRequestConfig } from 'axios';
declare module 'axios' {
export interface AxiosRequestConfig {
duplicateRequestValidation?: boolean;
duplicateRequestValidationTime?: number;
}
}
export const errorCodeType = function (code: number): string {
let message = '未知错误,请联系管理员处理!';
switch (code) {
case 302:
message = '接口重定向了!';
break;
case 400:
message = '(错误请求)Bad Request请求包含语法错误!';
break;
case 401:
message = '未授权,当前请求需要用户验证!';
break;
case 403:
message = '服务器已理解请求,但拒绝访问,您可能没有权限操作!';
break;
case 404:
message = '请求错误,服务器未找到该资源!';
break;
case 405:
message = '请求方法未允许!';
break;
case 408:
message = '服务器等候请求时发生超时!';
break;
case 409:
message = '系统已存在相同数据!';
break;
case 410:
message = '该资源已被删除!';
break;
case 413:
message = '请求实体过大!';
break;
case 414:
message = '请求的 URI 过长!';
break;
case 500:
message = '服务器端出错!';
break;
case 501:
message = '服务器不具备完成请求的功能!';
break;
case 502:
message = '错误网关!';
break;
case 503:
message = '由于临时的服务器维护或者过载,服务器当前无法处理请求!';
break;
case 504:
message = '网络超时!';
break;
case 505:
message = '服务器不支持请求中所用的 HTTP 协议版本!';
break;
default:
message = `其他错误 -- ${code}`;
}
return message;
};
import { AxiosHeaders, AxiosPromise, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import { Interceptors } from './request';
import { Method, RawAxiosRequestHeaders } from 'axios';
interface RequestConfigType {
url?: string;
method?: Method | string;
headers?: RawAxiosRequestHeaders | AxiosHeaders;
params?: any;
data?: any;
duplicateRequestValidation?: boolean;
duplicateRequestValidationTime?: number;
}
// 请求配置
export class HttpServer {
axios: any;
// 初始化对象 获取axios实例
constructor() {
this.axios = new Interceptors().getInterceptors();
}
// 简单封装一下方法
request(config: RequestConfigType): AxiosPromise {
return new Promise((resolve, reject) => {
this.axios(config as InternalAxiosRequestConfig)
.then((res: AxiosResponse) => {
resolve(res);
})
.catch((err: any) => {
reject(err);
});
});
}
post(config: RequestConfigType): AxiosPromise {
return new Promise((resolve, reject) => {
this.axios
.post(config.url, config.data, config as InternalAxiosRequestConfig)
.then((res: AxiosResponse) => {
resolve(res);
})
.catch((err: any) => {
reject(err);
});
});
}
get(config: RequestConfigType): AxiosPromise {
return new Promise((resolve, reject) => {
this.axios
.get(config.url, config as InternalAxiosRequestConfig)
.then((res: AxiosResponse) => {
resolve(res);
})
.catch((err: any) => {
reject(err);
});
});
}
delete(config: RequestConfigType): AxiosPromise {
return new Promise((resolve, reject) => {
this.axios
.delete(config.url, config as InternalAxiosRequestConfig)
.then((res: AxiosResponse) => {
resolve(res);
})
.catch((err: any) => {
reject(err);
});
});
}
put(config: RequestConfigType): AxiosPromise {
return new Promise((resolve, reject) => {
this.axios
.put(config.url, config.data, config as InternalAxiosRequestConfig)
.then((res: AxiosResponse) => {
resolve(res);
})
.catch((err: any) => {
reject(err);
});
});
}
patch(config: RequestConfigType): AxiosPromise {
return new Promise((resolve, reject) => {
this.axios
.patch(config.url, config.data, config)
.then((res: AxiosResponse) => {
resolve(res);
})
.catch((err: any) => {
reject(err);
});
});
}
}
const http = new HttpServer();
export default http;
import React from 'react';
import http from '../../utils/http';
const Home: React.FC = () => {
const getData = () => {
http
.post({
url: 'api/common/HotList',
duplicateRequestValidation: true,
})
.then((ts) => {
console.log(ts);
})
.catch((e) => {
console.error(e.message);
console.log();
});
};
return (
<>
这里是首页Home,路由/
>
);
};
export default Home;