【vue2小知识】实现axios的二次封装

【vue2小知识】实现axios的二次封装

一、背景与问题

在Vue2项目中,频繁的API调用是常态。直接使用axios存在以下痛点:

  1. 重复代码:每个请求都需要重复设置baseURL、headers、timeout等配置
  2. 错误处理碎片化:不同的接口可能需要不同的错误处理逻辑
  3. 缺乏统一管理:难以统一处理请求拦截、响应拦截、loading状态等通用逻辑
  4. 安全隐患:未对敏感请求进行加密处理,未处理CSRF攻击

为解决这些问题,我们需要对axios进行二次封装,创建一个统一的请求管理模块。这个模块应包含:

  • 配置管理
  • 请求拦截器
  • 响应拦截器
  • 错误处理
  • 加载状态管理
  • 安全增强

二、基本原理

axios的二次封装本质上是创建一个自定义的HTTP客户端,通过以下核心机制实现:

  1. 创建axios实例:通过axios.create()创建一个可配置的实例
  2. 请求拦截器:在请求发出前统一处理参数、添加token、处理loading状态
  3. 响应拦截器:在响应返回后统一处理数据格式、错误状态码
  4. 封装方法:通过axiosInstance.get/axiosInstance.post等方法封装常用请求方式
  5. 全局配置:统一配置baseURL、headers、timeout等参数

三、环境准备

确保项目中已安装axios:

npm install axios

创建项目结构:

src/
├── api/
│   └── index.js       # 请求封装文件
├── utils/
│   └── http.js        # 工具函数
├── services/
│   └── user.js        # 业务接口
└── main.js

四、核心实现

1. 基础封装(无loading)

// src/api/index.js
import axios from 'axios';

// 创建axios实例
const service = axios.create({
  baseURL: process.env.VUE_APP_API_BASE_URL, // 从.env获取
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json'
  }
});

// 请求拦截器
service.interceptors.request.use(config => {
  // 添加token
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  
  // 处理请求参数
  if (config.method === 'get' && config.params) {
    config.params = {
      ...config.params,
      timestamp: Date.now()
    };
  }
  
  return config;
}, error => {
  return Promise.reject(error);
});

// 响应拦截器
service.interceptors.response.use(response => {
  // 成功返回数据
  if (response.data.code === 200) {
    return response.data.data;
  }
  
  // 错误处理
  const code = response.data.code || 500;
  const message = response.data.message || '服务器错误';
  
  if (code === 401) {
    // 未授权处理
    localStorage.removeItem('token');
    window.location.href = '/login';
  }
  
  return Promise.reject({
    code,
    message
  });
});

export default service;

关键点解释:

  • 使用axios.create()创建实例,便于统一配置
  • 请求拦截器中添加token和处理参数,避免重复代码
  • 响应拦截器统一处理成功/失败逻辑,区分不同错误码
  • 返回的service实例可被其他模块直接调用

2. 带loading状态的封装

// src/utils/http.js
import service from './index';

export const request = async (config) => {
  const { loading = true, ...rest } = config;
  
  if (loading) {
    // 显示loading
    const loadingInstance = Vue.prototype.$loading({
      lock: true,
      text: '加载中...',
      spinner: 'el-icon-loading',
      background: 'rgba(0, 0, 0, 0.7)'
    });
    
    try {
      const result = await service(rest);
      return result;
    } finally {
      // 隐藏loading
      loadingInstance.close();
    }
  } else {
    return service(config);
  }
};

3. 带token刷新的封装

// src/utils/http.js
import service from './index';

export const request = async (config) => {
  const { loading = true, ...rest } = config;
  
  // 自动刷新token
  const refreshToken = async () => {
    try {
      const res = await service({
        url: '/api/refresh-token',
        method: 'post',
        data: {
          refresh_token: localStorage.getItem('refresh_token')
        }
      });
      
      if (res.code === 200) {
        localStorage.setItem('token', res.data.token);
        return res.data.token;
      }
    } catch (err) {
      // 刷新失败处理
      localStorage.removeItem('token');
      window.location.href = '/login';
      throw err;
    }
  };
  
  if (loading) {
    const loadingInstance = Vue.prototype.$loading({ /* ... */ });
    
    try {
      const token = localStorage.getItem('token');
      if (!token) {
        const refreshToken = await refreshToken();
        // 重新发起请求
        const result = await service(rest);
        return result;
      }
      
      const result = await service(rest);
      return result;
    } finally {
      loadingInstance.close();
    }
  } else {
    return service(config);
  }
};

五、完整案例

1. 业务接口封装

// src/services/user.js
import { request } from '@/utils/http';

export const login = (params) => {
  return request({
    url: '/api/user/login',
    method: 'post',
    data: params,
    loading: true
  });
};

export const getUserInfo = () => {
  return request({
    url: '/api/user/info',
    method: 'get',
    loading: true
  });
};

2. 组件调用示例

<template>
  <div>
    <button @click="login">登录</button>
    <button @click="fetchUserInfo">获取用户信息</button>
  </div>
</template>

<script>
import { login, getUserInfo } from '@/services/user';

export default {
  methods: {
    async login() {
      const res = await login({ username: 'test', password: '123456' });
      console.log('登录结果:', res);
    },
    
    async fetchUserInfo() {
      try {
        const info = await getUserInfo();
        console.log('用户信息:', info);
      } catch (err) {
        console.error('获取用户信息失败:', err);
      }
    }
  }
};
</script>

六、源码解析

1. 拦截器工作机制

axios拦截器本质上是中间件模式,通过链式调用处理请求/响应。每个拦截器函数接受一个config对象,返回新的config或Promise。

service.interceptors.request.use(
  config => {
    // 修改config
    return config;
  },
  error => {
    // 处理错误
    return Promise.reject(error);
  }
);

2. 异步请求处理

在request函数中使用async/await处理异步操作,确保错误能被正确捕获:

try {
  const result = await service(rest);
  return result;
} catch (err) {
  // 错误处理逻辑
}

七、进阶使用

1. 动态baseURL

根据环境动态切换API地址:

const service = axios.create({
  baseURL: process.env.NODE_ENV === 'production' 
    ? 'https://api.prod.example.com' 
    : 'https://api.dev.example.com',
  timeout: 10000
});

2. 自定义请求头

根据请求类型添加不同头信息:

if (config.method === 'post') {
  config.headers['X-Requested-With'] = 'XMLHttpRequest';
}

3. 请求重试机制

添加请求重试逻辑(需注意防抖):

const retry = (config, count = 3) => {
  return new Promise((resolve, reject) => {
    service(config).then(resolve).catch(err => {
      if (count > 0 && err.code === 'ECONNABORTED') {
        retry(config, count - 1).then(resolve).catch(reject);
      } else {
        reject(err);
      }
    });
  });
};

八、性能与工程实践

1. 性能优化方案

优化点方法效果
减少拦截器数量合并相似逻辑降低请求处理时间
缓存常用请求使用axios-cache-adapter减少网络请求
压缩请求参数去除空字段减少数据传输量
避免重复创建实例使用单例模式节省内存占用

2. 安全增强措施

安全风险解决方案
CSRF攻击添加XSRF-TOKEN头并验证
未授权访问验证Authorization头
数据泄露使用HTTPS加密传输
跨域请求配置CORS策略

3. 异常处理机制

try {
  const result = await request(config);
  console.log('成功:', result);
} catch (err) {
  if (err.code === 401) {
    console.log('未授权');
  } else if (err.code === 500) {
    console.log('服务器错误');
  } else {
    console.log('未知错误:', err.message);
  }
}

九、常见问题与踩坑

1. 常见错误及解决办法

错误类型现象解决方案
未处理错误页面崩溃使用try-catch包裹请求
未配置baseURL请求失败检查.env文件配置
未处理跨域403错误配置CORS或使用代理
未处理超时请求卡死设置合理的timeout值
未处理token过期401错误添加token刷新逻辑

2. 常见坑点

  • 拦截器顺序问题:请求拦截器应放在响应拦截器之前
  • 未处理网络错误:未捕获的网络错误会导致程序崩溃
  • 未处理并发请求:同一接口多次请求时未做防抖处理
  • 未处理接口变更:未及时更新API地址导致请求失败
  • 未处理异常状态码:未处理如400、404等状态码

十、最佳实践

1. 推荐方案

场景推荐方案
中小型项目基础封装 + loading状态
大型项目带token刷新的封装 + 缓存机制
安全要求高加密传输 + CSRF防护
需要统一管理创建独立的axios模块
需要错误日志添加错误日志记录功能

2. 实践建议

  • 使用axios-cache-adapter实现请求缓存
  • 使用axios-mock-adapter进行单元测试
  • 使用axios-logger记录请求日志
  • 使用axios-should-retry实现重试机制
  • 使用axios-rate-limit限制请求频率

十一、总结

通过axios的二次封装,我们实现了:

  1. 统一的API管理,减少重复代码
  2. 集中的错误处理机制,提高可维护性
  3. 灵活的扩展能力,支持不同业务需求
  4. 安全的请求处理,增强系统安全性
  5. 可扩展的架构,适应项目发展

在实际开发中,应根据项目规模和需求选择合适的封装方案。对于需要频繁调用接口的业务模块,建议使用带loading和token刷新的封装方案。同时需要注意避免常见错误,如未处理网络错误、未配置baseURL等。通过合理的性能优化和安全措施,可以显著提升系统的稳定性和安全性。

VUE , ios
最后修改于:2026年09月16日 01:28

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日