第十三章 React 封装 Axios 并做防重复提交 同一接口 [请求未返回结果、2秒内禁止重复提交](超详细)

一、专栏介绍

欢迎加入本专栏!本专栏将引领您快速上手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 (
    <>
      
这里是首页Home,路由/
); }; export default Home;

第十三章 React 封装 Axios 并做防重复提交 同一接口 [请求未返回结果、2秒内禁止重复提交](超详细)_第1张图片

你可能感兴趣的:(前端,javascript,react.js,React语法,前端框架)