【vue2小知识】实现axios的二次封装
一、背景与问题
在Vue2项目中,频繁的API调用是常态。直接使用axios存在以下痛点:
- 重复代码:每个请求都需要重复设置
baseURL、headers、timeout等配置 - 错误处理碎片化:不同的接口可能需要不同的错误处理逻辑
- 缺乏统一管理:难以统一处理请求拦截、响应拦截、loading状态等通用逻辑
- 安全隐患:未对敏感请求进行加密处理,未处理CSRF攻击
为解决这些问题,我们需要对axios进行二次封装,创建一个统一的请求管理模块。这个模块应包含:
- 配置管理
- 请求拦截器
- 响应拦截器
- 错误处理
- 加载状态管理
- 安全增强
二、基本原理
axios的二次封装本质上是创建一个自定义的HTTP客户端,通过以下核心机制实现:
- 创建axios实例:通过
axios.create()创建一个可配置的实例 - 请求拦截器:在请求发出前统一处理参数、添加token、处理loading状态
- 响应拦截器:在响应返回后统一处理数据格式、错误状态码
- 封装方法:通过
axiosInstance.get/axiosInstance.post等方法封装常用请求方式 - 全局配置:统一配置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的二次封装,我们实现了:
- 统一的API管理,减少重复代码
- 集中的错误处理机制,提高可维护性
- 灵活的扩展能力,支持不同业务需求
- 安全的请求处理,增强系统安全性
- 可扩展的架构,适应项目发展
在实际开发中,应根据项目规模和需求选择合适的封装方案。对于需要频繁调用接口的业务模块,建议使用带loading和token刷新的封装方案。同时需要注意避免常见错误,如未处理网络错误、未配置baseURL等。通过合理的性能优化和安全措施,可以显著提升系统的稳定性和安全性。