React 中基于 Axios 的二次封装(含请求守卫)

React 中基于 Axios 的二次封装(含请求守卫)

下面是一个完整的 Axios 二次封装方案,包含 GET、POST 等方法的封装,并加入了请求守卫(拦截器)功能。

1. 基础封装 (axiosInstance.js)

javascript 复制代码
import axios from 'axios';
import { message } from 'antd'; // 使用 Ant Design 的 message 作为示例,可替换为其他 UI 库或自定义

// 创建 axios 实例
const axiosInstance = axios.create({
  baseURL: process.env.REACT_APP_API_BASE_URL || '/api', // 基础路径
  timeout: 10000, // 请求超时时间
  withCredentials: true, // 跨域请求时是否需要使用凭证
});

// 请求拦截器
axiosInstance.interceptors.request.use(
  (config) => {
    // 在发送请求之前做些什么
    // 例如:添加 token
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    
    // 可以在这里添加全局请求参数或修改 config
    // config.params = { ...config.params, lang: 'zh-CN' };
    
    return config;
  },
  (error) => {
    // 对请求错误做些什么
    console.error('请求拦截器错误:', error);
    return Promise.reject(error);
  }
);

// 响应拦截器
axiosInstance.interceptors.response.use(
  (response) => {
    // 对响应数据做点什么
    // 假设后端返回的数据结构为 { code: 200, data: {}, message: '' }
    const res = response.data;
    
    if (res.code === 200) {
      return res.data; // 直接返回数据部分
    } else {
      // 处理业务错误
      message.error(res.message || '请求错误');
      return Promise.reject(new Error(res.message || 'Error'));
    }
  },
  (error) => {
    // 对响应错误做点什么
    console.error('响应拦截器错误:', error);
    
    if (error.response) {
      // 根据 HTTP 状态码处理不同错误
      switch (error.response.status) {
        case 401:
          message.error('未授权,请登录');
          // 这里可以跳转到登录页
          // window.location.href = '/login';
          break;
        case 403:
          message.error('拒绝访问');
          break;
        case 404:
          message.error('请求的资源不存在');
          break;
        case 500:
          message.error('服务器错误');
          break;
        default:
          message.error(error.response.data.message || '请求失败');
      }
    } else if (error.message.includes('timeout')) {
      message.error('请求超时');
    } else {
      message.error('网络错误');
    }
    
    return Promise.reject(error);
  }
);

export default axiosInstance;

2. 请求方法封装 (request.js)

javascript 复制代码
import axiosInstance from './axiosInstance';

/**
 * 封装 GET 请求
 * @param {string} url 请求地址
 * @param {object} params 请求参数
 * @param {object} config 额外配置
 * @returns Promise
 */
export function get(url, params = {}, config = {}) {
  return axiosInstance.get(url, { params, ...config });
}

/**
 * 封装 POST 请求
 * @param {string} url 请求地址
 * @param {object} data 请求体数据
 * @param {object} config 额外配置
 * @returns Promise
 */
export function post(url, data = {}, config = {}) {
  return axiosInstance.post(url, data, config);
}

/**
 * 封装 PUT 请求
 * @param {string} url 请求地址
 * @param {object} data 请求体数据
 * @param {object} config 额外配置
 * @returns Promise
 */
export function put(url, data = {}, config = {}) {
  return axiosInstance.put(url, data, config);
}

/**
 * 封装 DELETE 请求
 * @param {string} url 请求地址
 * @param {object} params 请求参数
 * @param {object} config 额外配置
 * @returns Promise
 */
export function del(url, params = {}, config = {}) {
  return axiosInstance.delete(url, { params, ...config });
}

/**
 * 封装上传请求
 * @param {string} url 请求地址
 * @param {FormData} formData 表单数据
 * @param {object} config 额外配置
 * @returns Promise
 */
export function upload(url, formData, config = {}) {
  return axiosInstance.post(url, formData, {
    headers: {
      'Content-Type': 'multipart/form-data',
    },
    ...config,
  });
}

// 可以继续添加其他封装方法...

3. 使用示例

javascript 复制代码
import React, { useEffect, useState } from 'react';
import { get, post } from './request';

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUsers = async () => {
      setLoading(true);
      try {
        const data = await get('/users');
        setUsers(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchUsers();
  }, []);

  const handleAddUser = async () => {
    try {
      const newUser = { name: 'New User', age: 25 };
      const data = await post('/users', newUser);
      console.log('添加成功:', data);
      // 可以刷新列表或做其他操作
    } catch (err) {
      console.error('添加失败:', err);
    }
  };

  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误: {error}</div>;

  return (
    <div>
      <button onClick={handleAddUser}>添加用户</button>
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

export default UserList;

4. 高级功能扩展

4.1 请求取消功能

javascript 复制代码
// 在 axiosInstance.js 中添加
const CancelToken = axios.CancelToken;
const sourceMap = new Map(); // 用于存储请求的取消函数

// 修改请求拦截器
axiosInstance.interceptors.request.use(
  (config) => {
    // 如果已有相同请求在执行,则取消之前的请求
    const requestKey = `${config.method}-${config.url}`;
    if (sourceMap.has(requestKey)) {
      const cancel = sourceMap.get(requestKey);
      cancel('取消重复请求');
    }
    
    // 创建新的取消令牌
    const source = CancelToken.source();
    config.cancelToken = source.token;
    sourceMap.set(requestKey, source.cancel);
    
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 修改响应拦截器
axiosInstance.interceptors.response.use(
  (response) => {
    // 请求成功后从 map 中移除对应的取消函数
    const config = response.config;
    const requestKey = `${config.method}-${config.url}`;
    sourceMap.delete(requestKey);
    
    return response;
  },
  (error) => {
    if (axios.isCancel(error)) {
      console.log('请求已取消:', error.message);
    }
    // 其他错误处理...
    return Promise.reject(error);
  }
);

// 导出取消请求的方法
export function cancelRequest(method, url) {
  const requestKey = `${method}-${url}`;
  if (sourceMap.has(requestKey)) {
    const cancel = sourceMap.get(requestKey);
    cancel('主动取消请求');
    sourceMap.delete(requestKey);
  }
}

4.2 请求重试机制

javascript 复制代码
// 在 axiosInstance.js 中添加
const retryCount = 2; // 重试次数
const retryDelay = 1000; // 重试延迟时间(ms)

// 修改请求拦截器或单独封装一个重试函数
async function retryRequest(config, retryNum = retryCount) {
  try {
    const response = await axiosInstance(config);
    return response;
  } catch (error) {
    if (retryNum <= 0) {
      throw error;
    }
    console.log(`请求失败,重试 ${retryNum} 次...`);
    await new Promise(resolve => setTimeout(resolve, retryDelay));
    return retryRequest(config, retryNum - 1);
  }
}

// 然后在需要的地方使用 retryRequest 代替直接调用 axiosInstance

5. TypeScript 版本

如果需要 TypeScript 版本,可以这样修改:

typescript 复制代码
// axiosInstance.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';

interface ResponseData<T = any> {
  code: number;
  data: T;
  message: string;
}

const axiosInstance: AxiosInstance = axios.create({
  baseURL: process.env.REACT_APP_API_BASE_URL || '/api',
  timeout: 10000,
  withCredentials: true,
});

axiosInstance.interceptors.request.use(
  (config: AxiosRequestConfig) => {
    const token = localStorage.getItem('token');
    if (token) {
      config.headers!.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error: AxiosError) => {
    return Promise.reject(error);
  }
);

axiosInstance.interceptors.response.use(
  (response: AxiosResponse<ResponseData>) => {
    const res = response.data;
    if (res.code === 200) {
      return res.data;
    } else {
      return Promise.reject(new Error(res.message || 'Error'));
    }
  },
  (error: AxiosError) => {
    // 错误处理...
    return Promise.reject(error);
  }
);

export default axiosInstance;

// request.ts
import axiosInstance from './axiosInstance';

export function get<T = any>(url: string, params?: any, config?: AxiosRequestConfig): Promise<T> {
  return axiosInstance.get(url, { params, ...config });
}

export function post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
  return axiosInstance.post(url, data, config);
}

// 其他方法...

这个封装方案提供了:

  1. 基础请求方法的封装
  2. 请求和响应拦截器(守卫)
  3. 错误统一处理
  4. 可扩展的高级功能(请求取消、重试等)
  5. TypeScript 支持(可选)

你可以根据实际项目需求进行调整和扩展。

相关推荐
早點睡3902 小时前
ReactNative项目OpenHarmony三方库集成实战:react-native-shimmer-placeholder
javascript·react native·react.js
哈__2 小时前
ReactNative项目OpenHarmony三方库集成实战:react-native-splash-screen
javascript·react native·react.js
浮游本尊2 小时前
React 18.x 学习计划 - 第十五天:GraphQL 与实时应用实战
学习·react.js·graphql
qq_406176142 小时前
React 状态管理完全指南:从入门到选型
前端·javascript·react.js
终端鹿12 小时前
Vue3 模板引用 (ref):操作 DOM 与子组件实例 从入门到精通
前端·javascript·vue.js
蜡台13 小时前
Vue 打包优化
前端·javascript·vue.js·vite·vue-cli
卷帘依旧14 小时前
JavaScript中this绑定问题详解
前端·javascript
西洼工作室14 小时前
React轮播图优化:通过延迟 + 动画的组合,彻底消除视觉上的闪烁感
前端·react.js·前端框架
yaaakaaang15 小时前
(八)前端,如此简单!---五组结构
前端·javascript