在现代前端开发中,与后端API的交互是构建动态应用的核心环节。Axios作为最流行的HTTP客户端之一,以其简洁的API和强大的功能在前端生态中占据重要地位。本文将全面探讨如何在Vue3项目中高效整合Axios,从基础配置到高级封装,从性能优化到安全实践,帮助开发者构建健壮的前后端交互层。
一、Axios基础与Vue3集成
1.1 Axios核心优势
基于Promise:支持异步/await语法
浏览器和Node.js通用:同构应用开发
请求/响应拦截:全局处理逻辑
自动转换JSON:简化数据处理
取消请求:优化用户体验
CSRF/XSRF防护:内置安全特性
1.2 安装与基础使用
npm install axios
# 或
yarn add axios
基础示例:
import axios from 'axios';
// GET请求
axios.get('/api/user/123')
.then(response => console.log(response.data))
.catch(error => console.error(error));
// POST请求
axios.post('/api/user', { name: '张三' })
.then(response => console.log('创建成功'));
1.3 Vue3中的集成方式
全局挂载(不推荐)
// main.js
import { createApp } from 'vue';
import axios from 'axios';
const app = createApp(App);
app.config.globalProperties.$axios = axios;
app.mount('#app');
// 组件中使用
export default {
mounted() {
this.$axios.get('/api/data');
}
}
组合式API推荐方式
// src/utils/http.js
import axios from 'axios';
const instance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000
});
export default instance;
// 组件中使用
import http from '@/utils/http';
export default {
setup() {
const fetchData = async () => {
try {
const { data } = await http.get('/endpoint');
console.log(data);
} catch (error) {
console.error('请求失败:', error);
}
};
return { fetchData };
}
}
二、高级配置与封装
2.1 创建可配置的Axios实例
// src/utils/http.js
import axios from 'axios';
import { ElMessage } from 'element-plus'; // 示例使用Element Plus消息组件
const http = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 30000,
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
});
// 请求拦截器
http.interceptors.request.use(
config => {
// 添加认证token
const token = localStorage.getItem('auth_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => {
return Promise.reject(error);
}
);
// 响应拦截器
http.interceptors.response.use(
response => {
// 处理业务逻辑错误
if (response.data?.code !== 200) {
ElMessage.error(response.data?.message || '业务错误');
return Promise.reject(response.data);
}
return response.data;
},
error => {
// 统一错误处理
if (error.response) {
switch (error.response.status) {
case 401:
ElMessage.error('未授权,请重新登录');
router.push('/login');
break;
case 403:
ElMessage.error('拒绝访问');
break;
case 500:
ElMessage.error('服务器错误');
break;
default:
ElMessage.error(`请求错误: ${error.message}`);
}
} else {
ElMessage.error('网络错误,请检查连接');
}
return Promise.reject(error);
}
);
export default http;
2.2 TypeScript支持
// src/types/api.d.ts
declare module 'axios' {
interface AxiosResponse<T = any> {
code: number;
message: string;
data: T;
// 其他自定义字段
}
}
// 封装响应类型
export interface ApiResponse<T> {
code: number;
message: string;
data: T;
timestamp: number;
}
// 使用示例
interface User {
id: number;
name: string;
email: string;
}
export const getUserInfo = (userId: number): Promise<ApiResponse<User>> => {
return http.get(`/user/${userId}`);
};
2.3 API模块化组织
src/
api/
modules/
auth.ts # 认证相关API
user.ts # 用户相关API
product.ts # 产品相关API
index.ts # API聚合导出
// src/api/modules/user.ts
import http from '@/utils/http';
import type { ApiResponse } from '@/types/api';
export interface UserProfile {
id: number;
name: string;
avatar: string;
// 其他字段
}
export const fetchUserProfile = (): Promise<ApiResponse<UserProfile>> => {
return http.get('/user/profile');
};
export const updateUserProfile = (
data: Partial<UserProfile>
): Promise<ApiResponse<UserProfile>> => {
return http.patch('/user/profile', data);
};
三、高级特性实战
3.1 取消请求
import axios, { CancelTokenSource } from 'axios';
let cancelTokenSource: CancelTokenSource;
export const searchProducts = (keyword: string) => {
// 取消之前的请求
if (cancelTokenSource) {
cancelTokenSource.cancel('取消重复请求');
}
cancelTokenSource = axios.CancelToken.source();
return http.get('/products/search', {
params: { q: keyword },
cancelToken: cancelTokenSource.token
});
};
// 组件中使用
const search = async () => {
try {
const { data } = await searchProducts(keyword.value);
products.value = data;
} catch (error) {
if (!axios.isCancel(error)) {
console.error('搜索失败:', error);
}
}
};
3.2 文件上传与进度监控
<template>
<input type="file" @change="handleFileUpload" />
<div v-if="uploadProgress > 0">
上传进度: {{ uploadProgress }}%
</div>
</template>
<script setup>
import { ref } from 'vue';
import http from '@/utils/http';
const uploadProgress = ref(0);
const handleFileUpload = async (event) => {
const file = event.target.files[0];
if (!file) return;
const formData = new FormData();
formData.append('file', file);
try {
const { data } = await http.post('/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
if (progressEvent.total) {
uploadProgress.value = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
}
}
});
console.log('上传成功:', data);
} catch (error) {
console.error('上传失败:', error);
} finally {
setTimeout(() => {
uploadProgress.value = 0;
}, 2000);
}
};
</script>
3.3 请求重试机制
// src/utils/http-retry.ts
import http from './http';
export const httpWithRetry = async (
config: any,
maxRetries = 3,
retryDelay = 1000
) => {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
const response = await http(config);
return response;
} catch (error) {
lastError = error;
// 只对网络错误和5xx错误重试
if (!error.response || error.response.status >= 500) {
if (i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, retryDelay));
retryDelay *= 2; // 指数退避
}
} else {
break;
}
}
}
throw lastError;
};
// 使用示例
import { httpWithRetry } from '@/utils/http-retry';
const fetchImportantData = async () => {
try {
const data = await httpWithRetry({
url: '/critical-data',
method: 'get'
}, 5); // 最多重试5次
console.log('获取关键数据成功:', data);
} catch (error) {
console.error('最终获取失败:', error);
}
};
四、性能优化与安全
4.1 请求缓存策略
// src/utils/http-cache.ts
import http from './http';
const cache = new Map();
export const httpWithCache = async (config: any, cacheKey: string, ttl = 60000) => {
const now = Date.now();
// 检查缓存
if (cache.has(cacheKey)) {
const { data, timestamp } = cache.get(cacheKey);
if (now - timestamp < ttl) {
return data;
}
}
// 发起新请求
try {
const response = await http(config);
cache.set(cacheKey, {
data: response,
timestamp: now
});
return response;
} catch (error) {
// 失败时返回缓存数据(如果有)
if (cache.has(cacheKey)) {
console.warn('使用缓存数据:', cacheKey);
return cache.get(cacheKey).data;
}
throw error;
}
};
// 使用示例
const fetchProductList = async () => {
return httpWithCache(
{ url: '/products', method: 'get' },
'product-list',
300000 // 5分钟缓存
);
};
4.2 并发请求优化
// 使用axios.all处理并发请求
const fetchDashboardData = async () => {
try {
const [userRes, ordersRes, statsRes] = await Promise.all([
http.get('/user/profile'),
http.get('/orders/latest'),
http.get('/dashboard/stats')
]);
return {
user: userRes.data,
orders: ordersRes.data,
stats: statsRes.data
};
} catch (error) {
console.error('获取仪表盘数据失败:', error);
throw error;
}
};
4.3 安全最佳实践
6.2 推荐架构
CSRF防护:
// Axios全局配置 http.defaults.xsrfCookieName = 'csrftoken'; http.defaults.xsrfHeaderName = 'X-CSRFToken';
JWT自动刷新:
// 响应拦截器中处理token刷新 let isRefreshing = false; let refreshSubscribers: any[] = []; http.interceptors.response.use( response => response, async error => { const { config, response } = error; if (response.status === 401 && !config._retry) { if (isRefreshing) { return new Promise(resolve => { refreshSubscribers.push((token: string) => { config.headers.Authorization = `Bearer ${token}`; resolve(http(config)); }); }); } config._retry = true; isRefreshing = true; try { const { data } = await http.post('/auth/refresh'); localStorage.setItem('auth_token', data.token); // 重试所有挂起的请求 refreshSubscribers.forEach(cb => cb(data.token)); refreshSubscribers = []; // 重试原始请求 config.headers.Authorization = `Bearer ${data.token}`; return http(config); } catch (refreshError) { // 刷新失败,跳转登录 localStorage.removeItem('auth_token'); router.push('/login'); return Promise.reject(refreshError); } finally { isRefreshing = false; } } return Promise.reject(error); } );
五、测试与调试
5.1 Mock服务配置
// 使用msw(Mock Service Worker) // src/mocks/handlers.js import { rest } from 'msw'; export const handlers = [ rest.get('/api/user', (req, res, ctx) => { return res( ctx.delay(150), ctx.json({ id: 1, name: 'Mock User' }) ); }), // 其他mock接口 ]; // src/mocks/browser.js import { setupWorker } from 'msw'; import { handlers } from './handlers'; export const worker = setupWorker(...handlers);
5.2 单元测试示例
// tests/unit/api.spec.ts import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { fetchUserProfile } from '@/api/modules/user'; describe('User API', () => { let mockAxios: MockAdapter; beforeEach(() => { mockAxios = new MockAdapter(axios); }); afterEach(() => { mockAxios.restore(); }); it('fetchUserProfile returns user data', async () => { const mockData = { id: 1, name: 'Test User' }; mockAxios.onGet('/user/profile').reply(200, { code: 200, data: mockData, message: 'success' }); const response = await fetchUserProfile(); expect(response.data).toEqual(mockData); }); it('handles 404 error', async () => { mockAxios.onGet('/user/profile').reply(404); await expect(fetchUserProfile()).rejects.toThrow(); }); });
六、总结与最佳实践
6.1 关键点总结
封装优于直接使用:创建配置好的Axios实例并统一管理
类型安全:为API响应添加TypeScript类型定义
模块化组织:按业务领域组织API模块
错误处理:全局拦截器中统一处理错误
安全考虑:实现CSRF防护和JWT自动刷新
性能优化:合理使用缓存、取消重复请求
src/ api/ modules/ # 按业务模块组织的API types/ # 类型定义 utils/ http.ts # Axios实例配置 interceptors # 拦截器 cache.ts # 缓存策略 retry.ts # 重试机制 stores/ # Pinia状态管理 modules/ # 与API模块对应的状态模块