一、专栏介绍 

欢迎加入本专栏!本专栏将引领您快速上手React,让我们一起放弃放弃的念头,开始学习之旅吧!我们将从搭建React项目开始,逐步深入讲解最核心的hooks,以及React路由、请求、组件封装以及UI(Ant Design)框架的使用。让我们一起掌握React,开启前端开发的全新篇章!

二、axios ⚡️ ⚡️ ⚡️

1、是什么 ❓ ❓ ❓

axios是一个基于Promise的HTTP网络请求库,可以用于浏览器和node.js。在服务端它使用原生node.js http模块, 而在客户端 (浏览端) 则使用XMLHttpRequest。它可以帮助我们更轻松、简单地发出 AJAX 请求。

2、为什么选择它   

1、它支持Promise API,能够处理异步请求,降低了回调地狱的问题。

2、它支持取消请求,可以在请求未完成时取消请求,避免浪费资源。

3、高开发效率,简化代码逻辑。

4、它提供了拦截器可以拦截请求和响应,可以在发送请求或接收响应之前对它们进行拦截和修改,从而实现统一处理或添加公共参数等功能。比如我们要做的同一接口防重复提交功能。

三、安装axios   

npm install axios --save

由于我之前写过一篇Vue3的,内容呢其实一摸一样的,无非就是里面使用的element框架的message提示替换一下,其次就是环境变量自己定义一下就好,我这里就直接贴源码,要看详细说明的朋友请到Vite + Vue3 封装 Axios 并做防重复提交 同一接口 [请求未返回结果、2秒内禁止重复提交](超详细)_vite封装axios-CSDN博客

四、封装axios   

为什么还要封装axios,因为它默认情况下不知道我们的业务需求,所以封装它最大的目的是适应项目需求。其次封装它可以将重复的代码抽象成一个方法,减少代码量,提高代码的可读性和可维护性以及复用性。统一处理响应错误、全局loading、设置请求头、请求超时处理等。

1、新建文件src/utils/http/request.ts  

代码中基本上该注释的地方我都有写注释,因为我这里是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;

}

}

2、新建src/utils/http/shims.axios.d.ts文件  

import { AxiosRequestConfig } from 'axios';

declare module 'axios' {

export interface AxiosRequestConfig {

duplicateRequestValidation?: boolean;

duplicateRequestValidationTime?: number;

}

}

3、新建src/utils/http/error-code-type.ts文件  

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;

};

4、新建src/utils/http/index.ts文件  

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 (

<>

style={{

width: '100%',

height: '100%',

display: 'flex',

alignItems: 'center',

justifyContent: 'center',

fontSize: '45px',

}}

>

这里是首页Home,路由/