1. 为什么选择 Axios 而不是原生 fetch?
作为一名经历过无数次接口联调的前端开发者,我可以明确告诉你:在 2023 年的现代前端开发中,Axios 已经成为事实上的标准 HTTP 客户端库。这不仅仅是因为它好用,更是因为它解决了原生 fetch API 的一系列痛点问题。
1.1 fetch 的三大致命伤
第一伤:错误处理反人类
javascript复制// fetch 的错误处理需要手动检查状态码
fetch('/api/data')
.then(response => {
if (!response.ok) { // 必须手动检查
throw new Error('请求失败');
}
return response.json();
})
.catch(error => {
// 这里捕获的可能是网络错误,也可能是业务错误
console.error('出错了,但不知道具体是什么错', error);
});
而 Axios 的处理方式直观得多:
javascript复制axios.get('/api/data')
.then(response => {
// 2xx 状态码才会进入这里
console.log(response.data);
})
.catch(error => {
// 4xx/5xx 错误都会进入这里
if (error.response) {
console.log('服务器返回错误:', error.response.status);
} else {
console.log('网络错误:', error.message);
}
});
第二伤:JSON 数据需要二次解析
javascript复制// fetch 需要两次 then 才能拿到数据
fetch('/api/data')
.then(response => response.json()) // 第一次解析
.then(data => {
console.log(data); // 第二次才能使用
});
Axios 直接帮你完成了这个工作:
javascript复制axios.get('/api/data')
.then(response => {
console.log(response.data); // 直接就是解析好的对象
});
第三伤:缺少实用功能
- 没有请求超时设置
- 没有请求取消功能
- 没有上传进度监控
- 没有自动重试机制
1.2 Axios 的六大杀手锏
- 自动转换 JSON 数据:服务器返回的 JSON 会自动转换为 JavaScript 对象
- 智能错误处理:HTTP 状态码非 2xx 自动进入 catch
- 拦截器机制:可以在请求和响应时插入处理逻辑
- 客户端 XSRF 防护:自动防御跨站请求伪造攻击
- 取消请求:可以中止已经发出的请求
- 多环境支持:同时在浏览器和 Node.js 环境中工作
实际项目经验:在我参与的一个电商项目中,使用 Axios 的拦截器统一处理了 Token 刷新逻辑,使得前端在 Token 过期时能自动刷新而不需要用户重新登录,用户体验提升显著。
2. 从安装到实战:Axios 完全指南
2.1 安装与基础配置
现代前端项目安装
bash复制# 使用 npm
npm install axios
# 使用 yarn
yarn add axios
# 使用 pnpm
pnpm add axios
CDN 引入方式(适合简单项目)
html复制<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
推荐的项目级配置
javascript复制// src/utils/axios.js
import axios from 'axios';
const instance = axios.create({
baseURL: process.env.VUE_APP_API_BASE_URL || '/api',
timeout: 10000, // 10秒超时
headers: {
'Content-Type': 'application/json',
},
});
export default instance;
2.2 四种核心请求方式详解
GET 请求:获取数据
javascript复制// 基本用法
axios.get('/user?ID=12345')
.then(response => {
console.log(response.data);
});
// 推荐写法:参数对象化
axios.get('/user', {
params: {
ID: 12345,
type: 'vip'
}
});
// 异步函数写法(更现代)
async function getUser() {
try {
const response = await axios.get('/user/12345');
console.log(response.data);
} catch (error) {
console.error(error);
}
}
POST 请求:创建数据
javascript复制// 提交 JSON 数据
axios.post('/user', {
firstName: '张',
lastName: '三'
});
// 提交 FormData
const formData = new FormData();
formData.append('username', '张三');
formData.append('avatar', fileInput.files[0]);
axios.post('/user/avatar', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
PUT/PATCH 请求:更新数据
javascript复制// PUT 通常用于替换整个资源
axios.put('/user/123', {
name: '新名字',
age: 30
});
// PATCH 用于部分更新
axios.patch('/user/123', {
age: 31 // 只更新年龄字段
});
DELETE 请求:删除数据
javascript复制axios.delete('/user/123')
.then(() => {
console.log('删除成功');
});
2.3 拦截器高级用法
请求拦截器实战
javascript复制// 添加请求拦截器
axios.interceptors.request.use(
config => {
// 在发送请求之前做些什么
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// 如果是 GET 请求,添加时间戳防止缓存
if (config.method === 'get') {
config.params = {
...config.params,
_t: Date.now()
};
}
return config;
},
error => {
// 对请求错误做些什么
return Promise.reject(error);
}
);
响应拦截器实战
javascript复制// 添加响应拦截器
axios.interceptors.response.use(
response => {
// 对响应数据做点什么
if (response.data.code !== 0) {
return Promise.reject(response.data);
}
return response.data.data; // 直接返回业务数据
},
error => {
// 对响应错误做点什么
if (error.response) {
switch (error.response.status) {
case 401:
// 跳转到登录页
window.location.href = '/login';
break;
case 403:
// 显示无权限提示
alert('无权限访问');
break;
case 500:
// 显示服务器错误
alert('服务器开小差了,请稍后再试');
break;
}
}
return Promise.reject(error);
}
);
3. 企业级 Axios 配置方案
3.1 创建多个 Axios 实例
在实际项目中,我们通常需要与多个后端服务通信,每个服务可能有不同的配置:
javascript复制// src/api/main.js - 主服务
import axios from 'axios';
const mainService = axios.create({
baseURL: process.env.VUE_APP_MAIN_API,
timeout: 10000
});
// src/api/payment.js - 支付服务
const paymentService = axios.create({
baseURL: process.env.VUE_APP_PAYMENT_API,
timeout: 15000
});
// 可以给不同实例添加不同的拦截器
paymentService.interceptors.request.use(config => {
config.headers['X-Payment-Token'] = getPaymentToken();
return config;
});
3.2 请求取消机制
在单页应用中,当用户快速切换页面时,可能需要取消之前的请求:
javascript复制const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/123', {
cancelToken: new CancelToken(function executor(c) {
cancel = c; // 保存取消函数
})
});
// 取消请求
cancel('用户取消了操作');
3.3 上传进度监控
对于大文件上传,显示上传进度可以提升用户体验:
javascript复制const formData = new FormData();
formData.append('file', largeFile);
axios.post('/upload', formData, {
onUploadProgress: progressEvent => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(`${percentCompleted}% 上传完成`);
}
});
4. 性能优化与最佳实践
4.1 请求节流与缓存
javascript复制// 简单的请求缓存实现
const cache = new Map();
async function getWithCache(url) {
if (cache.has(url)) {
return cache.get(url);
}
const response = await axios.get(url);
cache.set(url, response.data);
return response.data;
}
4.2 错误重试机制
javascript复制axios.interceptors.response.use(null, error => {
const config = error.config;
// 如果不是网络错误或者没有配置重试次数,直接拒绝
if (!error.response || !config.retry) {
return Promise.reject(error);
}
// 设置重试次数
config.__retryCount = config.__retryCount || 0;
// 检查是否达到最大重试次数
if (config.__retryCount >= config.retry) {
return Promise.reject(error);
}
// 增加重试计数器
config.__retryCount += 1;
// 创建新的 Promise 来实现延迟重试
const backoff = new Promise(resolve => {
setTimeout(() => {
resolve();
}, config.retryDelay || 1000);
});
// 返回 Promise,在延迟后重试请求
return backoff.then(() => axios(config));
});
4.3 TypeScript 支持
Axios 天然支持 TypeScript,我们可以进一步增强类型安全:
typescript复制interface User {
id: number;
name: string;
email: string;
}
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
async function getUser(id: number): Promise<User> {
const response = await axios.get<ApiResponse<User>>(`/users/${id}`);
return response.data.data;
}
5. 常见问题与解决方案
5.1 跨域问题处理
虽然跨域主要是后端需要解决的问题,但前端可以这样配置:
javascript复制axios.defaults.withCredentials = true; // 允许携带 cookie
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
5.2 文件下载处理
javascript复制axios.get('/export', {
responseType: 'blob' // 重要
}).then(response => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'report.xlsx');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
5.3 并发请求处理
javascript复制// 使用 axios.all
axios.all([
axios.get('/user/123'),
axios.get('/user/123/permissions')
]).then(axios.spread((userRes, permissionsRes) => {
console.log('用户数据:', userRes.data);
console.log('权限数据:', permissionsRes.data);
}));
// 使用 Promise.all
Promise.all([
axios.get('/products'),
axios.get('/categories')
]).then(([productsRes, categoriesRes]) => {
// 处理结果
});
5.4 测试中的 Mock 处理
在测试环境中,我们可以这样 Mock Axios:
javascript复制// 使用 jest.mock
jest.mock('axios');
test('fetch user', async () => {
axios.get.mockResolvedValue({
data: { id: 1, name: '张三' }
});
const user = await getUser(1);
expect(user.name).toBe('张三');
});
6. Axios 生态与替代方案
虽然 Axios 是目前最流行的 HTTP 客户端,但也存在一些替代方案:
- fetch API:原生但功能有限,适合简单场景
- ky:基于 fetch 的轻量级封装
- superagent:Node.js 中常用的 HTTP 客户端
- umi-request:阿里开源的请求库
选择建议:
- 如果是新项目,直接使用 Axios
- 如果对包大小极其敏感,可以考虑 ky
- 如果是 Node.js 项目,superagent 也是个不错的选择
在实际项目中,我们团队通过封装 Axios 实现了:
- 自动错误上报
- 接口性能监控
- 请求重试机制
- 接口文档自动生成
这些扩展功能使得我们的前端网络层更加健壮和可维护。