告别复杂:用 Axios 高效管理前端 API 请求
序言:前端开发中 API 请求的“痛”与“痒”
在当今高度交互的 Web 应用中,前端不再仅仅是页面的展示者,更是数据的生产者、消费者与协调者。与后端 API 的高效通信,是构建流畅、响应迅速用户体验的基石。然而,管理这些 API 请求,却常常成为前端开发中的一个“痛点”:
- 原生 XMLHttpRequest 的繁琐:冗长的代码、回调地狱、错误处理困难,让开发者苦不堪言。
- Fetch API 的局限:尽管原生 Promise 化带来了简洁,但缺少请求拦截、响应拦截、取消请求、统一错误处理等高级功能,使得在复杂业务场景下仍需大量手动封装。
- 项目规模膨胀带来的管理混乱:随着应用功能迭代,API 接口数量激增,如何统一管理请求头、超时设置、错误提示、Loading 状态,成为了维护性和扩展性的巨大挑战。
- 跨域、并发、鉴权等问题:这些常见的网络请求问题,如果处理不当,将大大增加开发成本和调试难度。
正是为了解决这些痛点,一个强大的、基于 Promise 的 HTTP 客户端——Axios 应运而生。它不仅在浏览器端表现卓越,也完美支持 Node.js 环境,以其简洁的 API、丰富的功能和出色的可扩展性,迅速成为前端社区管理 API 请求的首选利器。
本文将带领你深入探索 Axios 的世界,从基础用法到高级特性,从核心优势到最佳实践,全方位展示如何利用 Axios “告别复杂”,高效、优雅地管理前端 API 请求,从而将更多精力投入到核心业务逻辑的构建中。
第一章:告别原生 XHR 的繁琐与 Fetch 的局限
在深入 Axios 之前,让我们快速回顾一下它所替代的或补充的技术,以便更好地理解 Axios 的价值。
1.1 XMLHttpRequest:古典时代的繁琐舞步
XMLHttpRequest(XHR)是 Web 浏览器提供的第一个 API,允许 JavaScript 发出 HTTP 请求。它承载了 Web 异步通信的早期梦想,但其 API 设计在今天看来已显得相当繁琐和反模式:
``javascriptHTTP error! status: ${xhr.status}`), null);
function fetchDataWithXHR(url, callback) {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true); // true表示异步
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
callback(null, JSON.parse(xhr.responseText));
} else {
callback(new Error(
}
};
xhr.onerror = function() {
callback(new Error(‘Network error!’), null);
};
xhr.send();
}
// 使用示例:
fetchDataWithXHR(‘/api/data’, (error, data) => {
if (error) {
console.error(error);
} else {
console.log(data);
}
});
“`
XHR 的痛点:
* 回调地狱 (Callback Hell):大量嵌套的回调函数使得代码难以阅读和维护。
* 状态管理复杂:需要手动监听 readyState 和 status 变化。
* 缺乏 Promise 支持:与现代异步编程范式格格不入。
* 错误处理分散:onload、onerror、onprogress 等事件需要分别处理。
* 请求头、参数等配置繁琐:需要手动设置 setRequestHeader。
1.2 Fetch API:现代异步的初试啼声
为了解决 XHR 的痛点,ES6 引入了 Promise,并伴随着 Fetch API 的出现。Fetch API 提供了一个更简洁、更现代的 HTTP 请求接口,它基于 Promise 设计,大大简化了异步操作:
``javascriptHTTP error! status: ${response.status}`);
function fetchDataWithFetch(url) {
return fetch(url)
.then(response => {
// Fetch 不会把 4xx/5xx 状态码视为错误,需要手动检查
if (!response.ok) {
throw new Error(
}
return response.json(); // 自动解析JSON
})
.catch(error => {
console.error(‘Fetch error:’, error);
throw error; // 再次抛出以便外部捕获
});
}
// 使用示例:
fetchDataWithFetch(‘/api/data’)
.then(data => console.log(data))
.catch(error => console.error(‘Caught in main:’, error));
// 或使用 async/await (更现代的写法)
async function fetchDataWithFetchAsync(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(HTTP error! status: ${response.status});
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error(‘Fetch error (async):’, error);
}
}
“`
Fetch API 的进步与局限:
进步:
* Promise 化:与现代异步编程范式完美融合,摆脱回调地狱。
* 简洁的语法:API 设计更贴近语义,易于理解。
* 更强大的功能:支持 Request 和 Response 对象,提供了更多控制。
局限:
* 默认不处理 HTTP 错误状态码:对于 4xx 或 5xx 的响应,Fetch 不会将它们视为网络错误,Promise 不会进入 catch 块。需要手动检查 response.ok。
* 没有请求拦截器和响应拦截器:这意味着无法在发出请求前或收到响应后进行全局性的统一处理(如添加 token、统一错误提示、Loading 状态管理)。
* 没有内置的请求取消机制:早期版本没有,后虽引入 AbortController,但仍需手动创建和管理。
* 没有内置的请求超时设置:需要结合 AbortController 和 Promise.race 手动实现。
* 不支持上传/下载进度:难以实现文件上传的进度条。
* 默认不发送 Cookie:跨域请求需要设置 credentials: 'include'。
* JSON 转换需手动调用:response.json() response.text() 等。
正是为了弥补 Fetch API 的这些局限,Axios 才能够脱颖而出,提供一个更为完善、强大的 HTTP 请求解决方案。
第二章:初识 Axios:安装与基本使用
Axios 是一个基于 Promise 的 HTTP 客户端,适用于浏览器和 Node.js。它具备许多 Fetch 和 XHR 所没有的强大功能。
2.1 安装 Axios
在你的前端项目中,可以通过 npm、yarn 或 CDN 引入 Axios:
使用 npm/yarn:
“`bash
npm install axios
或者
yarn add axios
“`
在项目中引入:
javascript
import axios from 'axios'; // ES Modules
// 或
const axios = require('axios'); // CommonJS
使用 CDN:
“`html
``window.axios` 即可使用。
引入后,
2.2 基本 GET 请求
最简单的 GET 请求,通常用于获取数据:
“`javascript
import axios from ‘axios’;
// 1. 使用 .then/.catch 链式调用
axios.get(‘/api/users’)
.then(response => {
console.log(‘GET 请求成功 (then/catch):’, response.data);
})
.catch(error => {
console.error(‘GET 请求失败 (then/catch):’, error);
});
// 2. 使用 async/await (更推荐的现代异步写法)
async function getUsers() {
try {
const response = await axios.get(‘/api/users’, {
params: {
page: 1,
pageSize: 10
},
headers: {
‘X-Custom-Header’: ‘foobar’
}
});
console.log(‘GET 请求成功 (async/await):’, response.data);
// response 对象包含:
// data: 后端返回的实际数据
// status: HTTP 状态码,如 200
// statusText: HTTP 状态消息,如 ‘OK’
// headers: 响应头
// config: 请求的配置对象
// request: 原始 XMLHttpRequest 对象 (浏览器环境)
} catch (error) {
console.error(‘GET 请求失败 (async/await):’, error.message);
// 错误处理更详细:
if (error.response) {
// 请求已发出,但服务器响应的状态码不在 2xx 范围内
console.error(‘服务器响应错误:’, error.response.data);
console.error(‘状态码:’, error.response.status);
console.error(‘头部信息:’, error.response.headers);
} else if (error.request) {
// 请求已发出但没有收到响应
// error.request 在浏览器中是 XMLHttpRequest 的实例,在 node.js 中是 http.ClientRequest 的实例
console.error(‘请求未收到响应:’, error.request);
} else {
// 在设置请求时发生了错误
console.error(‘请求配置错误:’, error.message);
}
console.error(‘请求配置:’, error.config);
}
}
getUsers();
``{ page: 1, pageSize: 10 }
**注意:** Axios 自动将 URL 参数对象(如)转换为查询字符串(如?page=1&pageSize=10`)。
2.3 基本 POST 请求
POST 请求通常用于向服务器发送数据以创建或更新资源:
“`javascript
import axios from ‘axios’;
async function createUser(userData) {
try {
const response = await axios.post(‘/api/users’, userData, {
headers: {
‘Content-Type’: ‘application/json’ // Axios 默认就是 application/json
}
});
console.log(‘POST 请求成功:’, response.data);
} catch (error) {
console.error(‘POST 请求失败:’, error);
}
}
createUser({ name: ‘张三’, age: 30 });
``Content-Type
**注意:** Axios 默认会将发送的 JavaScript 对象自动序列化为 JSON 字符串,并将设置为application/json。无需手动JSON.stringify()`。
2.4 其他 HTTP 方法
Axios 也提供了其他 HTTP 方法的便捷函数:
“`javascript
// PUT 请求:更新资源
axios.put(‘/api/users/123’, { name: ‘李四’, age: 31 })
.then(response => console.log(‘PUT 成功:’, response.data))
.catch(error => console.error(‘PUT 失败:’, error));
// DELETE 请求:删除资源
axios.delete(‘/api/users/123’)
.then(response => console.log(‘DELETE 成功:’, response.data))
.catch(error => console.error(‘DELETE 失败:’, error));
// PATCH 请求:部分更新资源
axios.patch(‘/api/users/123’, { age: 32 })
.then(response => console.log(‘PATCH 成功:’, response.data))
.catch(error => console.error(‘PATCH 失败:’, error));
“`
2.5 并发请求:axios.all 和 axios.spread
当需要同时发起多个独立的请求,并在所有请求都完成后统一处理结果时,可以使用 axios.all (实际上是 Promise.all 的一个封装) 和 axios.spread:
“`javascript
async function fetchMultipleData() {
try {
const [usersResponse, productsResponse] = await axios.all([
axios.get(‘/api/users’),
axios.get(‘/api/products’)
]);
console.log('用户数据:', usersResponse.data);
console.log('产品数据:', productsResponse.data);
// 使用 axios.spread
axios.all([
axios.get('/api/users'),
axios.get('/api/products')
])
.then(axios.spread((users, products) => {
console.log('用户数据 (spread):', users.data);
console.log('产品数据 (spread):', products.data);
}));
} catch (error) {
console.error('并发请求失败:', error);
}
}
fetchMultipleData();
“`
第三章:Axios 的核心优势与高级特性
Axios 之所以强大,不仅仅在于其基础用法简洁,更在于其提供了一系列高级功能,极大地提升了前端 API 请求的管理效率和灵活性。
3.1 请求与响应拦截器 (Interceptors)
拦截器是 Axios 最核心、最有用的功能之一。它允许你在请求被发送到服务器之前,或在响应被 then 或 catch 处理之前,对请求或响应进行统一的处理。
用途:
* 请求拦截器:
* 统一添加请求头:例如,添加认证 Token。
* 统一处理请求参数:例如,加密参数,或添加公共参数。
* 显示/隐藏 Loading 状态:在请求开始时显示 Loading,请求结束时隐藏。
* 处理重复请求:避免短时间内发送多次相同请求。
* 响应拦截器:
* 统一错误处理:根据 HTTP 状态码或后端返回的业务状态码进行统一的错误提示。
* 数据格式化:对后端返回的数据进行预处理或统一解密。
* Token 过期处理:检测 Token 是否过期,进行刷新或重定向到登录页。
* 隐藏 Loading 状态:在响应到达时隐藏 Loading。
示例:
``javascriptBearer ${token}`;
// 请求拦截器
axios.interceptors.request.use(
config => {
// 在发送请求之前做些什么
// 例如:添加认证 Token
const token = localStorage.getItem('authToken');
if (token) {
config.headers.Authorization =
}
// 例如:显示全局 Loading
console.log('请求发送前,显示 Loading...');
// 如果有需要,可以在这里通过Vuex/Redux等管理Loading状态
return config; // 必须返回 config 对象
},
error => {
// 对请求错误做些什么
console.error('请求发送错误:', error);
return Promise.reject(error);
}
);
// 响应拦截器
axios.interceptors.response.use(
response => {
// 对响应数据做些什么
// 例如:隐藏全局 Loading
console.log(‘收到响应,隐藏 Loading…’);
// 对业务状态码进行判断和处理
if (response.data && response.data.code !== 0) { // 假设 code: 0 表示成功
console.error('业务错误:', response.data.message);
// 这里可以弹窗提示用户
return Promise.reject(new Error(response.data.message || 'Error'));
}
return response; // 必须返回 response 对象
},
error => {
// 对响应错误做些什么
console.error('响应错误:', error);
// 隐藏全局 Loading
console.log('响应错误,隐藏 Loading...');
// 根据 HTTP 状态码进行统一错误处理
if (error.response) {
switch (error.response.status) {
case 401:
console.error('未授权,请重新登录');
// router.push('/login'); // 重定向到登录页
break;
case 403:
console.error('拒绝访问');
break;
case 404:
console.error('请求资源不存在');
break;
case 500:
console.error('服务器内部错误');
break;
default:
console.error(`未知错误: ${error.response.status}`);
}
} else if (error.request) {
// 请求已经发送了,但没有收到响应
console.error('请求超时或网络中断');
} else {
// 在设置请求时发生了一些事情,导致了一个错误
console.error('Axios 请求配置错误', error.message);
}
return Promise.reject(error);
}
);
``axios.interceptors.request.eject(interceptorId)
**移除拦截器:**或axios.interceptors.response.eject(interceptorId)`。当拦截器返回一个函数时,这个函数就是移除拦截器的 ID。
3.2 取消请求 (Cancelling Requests)
在某些场景下,例如用户快速切换页面、搜索框输入频繁、或组件卸载时,我们需要取消正在进行的请求,以避免不必要的网络开销和潜在的竞态条件。
Axios 提供了两种取消请求的方式:
1. 使用 CancelToken (旧版,但仍可用)
每个请求创建一个 CancelToken 实例,通过 source.token 传递给请求,并通过 source.cancel() 来取消请求。
“`javascript
import axios from ‘axios’;
const CancelToken = axios.CancelToken;
let cancel; // 用于存储取消函数
async function fetchDataWithCancelToken() {
// 如果已经有请求正在进行,先取消它
if (cancel) {
cancel(‘Operation canceled by the user.’);
}
try {
const response = await axios.get('/api/long-running-task', {
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c; // 将 cancel 函数赋值给外部变量,以便后续调用
})
});
console.log('数据获取成功:', response.data);
} catch (thrown) {
if (axios.isCancel(thrown)) {
console.log('请求被取消:', thrown.message);
} else {
console.error('请求失败:', thrown);
}
}
}
// 模拟触发请求
fetchDataWithCancelToken();
// 模拟在一段时间后取消请求
setTimeout(() => {
if (cancel) {
cancel(‘Manual cancellation after 1s’);
}
}, 1000);
“`
2. 使用 AbortController (新版,推荐)
这是 Web 标准的 AbortController API,Axios 1.x 版本开始支持,并推荐使用。
“`javascript
import axios from ‘axios’;
const controller = new AbortController(); // 创建 AbortController 实例
async function fetchDataWithAbortController() {
try {
const response = await axios.get(‘/api/another-long-task’, {
signal: controller.signal // 将 signal 传递给请求配置
});
console.log(‘数据获取成功 (AbortController):’, response.data);
} catch (error) {
if (axios.isCancel(error)) { // 同样使用 axios.isCancel 来判断是否为取消错误
console.log(‘请求被取消 (AbortController):’, error.message);
} else {
console.error(‘请求失败 (AbortController):’, error);
}
}
}
fetchDataWithAbortController();
// 模拟在一段时间后取消请求
setTimeout(() => {
controller.abort(‘Operation canceled by AbortController’); // 调用 abort 方法取消请求
}, 1500);
“`
3.3 全局配置与实例配置 (axios.defaults 与 axios.create())
为了避免在每个请求中重复设置相同的配置(如 baseURL、timeout、headers 等),Axios 提供了两种配置方式:
1. 全局默认配置 (axios.defaults)
适用于整个应用的所有 Axios 请求。
“`javascript
import axios from ‘axios’;
axios.defaults.baseURL = ‘https://api.example.com’;
axios.defaults.timeout = 5000; // 5秒超时
axios.defaults.headers.common[‘X-Requested-With’] = ‘XMLHttpRequest’; // 常用头部
axios.defaults.headers.post[‘Content-Type’] = ‘application/x-www-form-urlencoded’; // POST 默认头部
// 此后所有 axios.get/post 等请求都会继承这些配置
axios.get(‘/users/1’).then(res => console.log(res.data));
“`
2. 创建 Axios 实例 (axios.create())
当你需要连接多个不同的后端服务,或者某些模块的请求需要特定的配置和拦截器时,创建 Axios 实例是最佳选择。每个实例都有自己独立的配置和拦截器,互不影响。
“`javascript
import axios from ‘axios’;
// 创建一个用于用户服务的实例
const userService = axios.create({
baseURL: ‘https://user-api.example.com’,
timeout: 10000,
headers: {
‘Custom-User-Agent’: ‘UserService’
}
});
// 为这个实例添加独立的拦截器
userService.interceptors.request.use(config => {
console.log(‘用户服务请求拦截器:’, config.url);
// … 其他用户服务特有的请求处理
return config;
});
// 创建一个用于商品服务的实例
const productService = axios.create({
baseURL: ‘https://product-api.example.com’,
timeout: 8000
});
productService.interceptors.response.use(response => {
console.log(‘商品服务响应拦截器:’, response.config.url);
// … 其他商品服务特有的响应处理
return response;
});
// 使用不同的实例发送请求
userService.get(‘/profile’).then(res => console.log(‘用户资料:’, res.data));
productService.get(‘/list’).then(res => console.log(‘商品列表:’, res.data));
// 原生的 axios 实例不受影响
axios.get(‘/global/info’).then(res => console.log(‘全局信息:’, res.data)); // 使用全局配置
``axios.create()` 是一个非常重要的特性,它使得在一个大型项目中管理多套 API 变得简单而清晰。
3.4 错误处理的艺术
Axios 默认将所有非 2xx 状态码的响应视为错误,并拒绝 Promise,使得错误能够统一在 catch 块中处理。在错误对象中,你可以获取到详细的信息:
error.response:如果请求已发出且服务器响应了状态码(不在 2xx 范围内)。包含data,status,headers等。error.request:如果请求已发出但没有收到响应(例如网络中断、请求超时)。error.message:错误信息字符串。error.config:生成该错误的请求配置。
结合响应拦截器,可以实现非常优雅和健壮的全局错误处理机制,前面拦截器示例已展示。
3.5 请求超时 (timeout)
通过 timeout 配置项,可以为请求设置超时时间(单位:毫秒)。如果请求在指定时间内没有收到响应,Axios 会自动中断请求并抛出错误。
javascript
axios.get('/api/slow-response', {
timeout: 2000 // 2秒后超时
})
.then(response => console.log(response.data))
.catch(error => {
if (axios.isCancel(error)) {
console.error('请求被取消或超时:', error.message);
} else if (error.code === 'ECONNABORTED') { // 网络超时错误码
console.error('请求超时');
} else {
console.error('其他错误:', error);
}
});
3.6 上传与下载进度 (onUploadProgress, onDownloadProgress)
在需要显示文件上传或下载进度条的场景中,Axios 提供了专门的配置项:
“`javascript
const formData = new FormData();
formData.append(‘file’, fileInput.files[0]);
axios.post(‘/upload’, formData, {
onUploadProgress: progressEvent => {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
console.log(上传进度: ${percentCompleted}%);
// 更新 UI 进度条
},
onDownloadProgress: progressEvent => {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
console.log(下载进度: ${percentCompleted}%);
}
})
.then(response => console.log(‘上传成功:’, response.data))
.catch(error => console.error(‘上传失败:’, error));
“`
3.7 跨域请求与凭证 (withCredentials)
对于需要发送 Cookie 或 HTTP 认证信息(例如 Session ID)的跨域请求,需要设置 withCredentials 为 true。
javascript
axios.get('/api/data', {
withCredentials: true // 允许跨域请求携带凭证(如 Cookie)
})
.then(response => console.log(response.data))
.catch(error => console.error(error));
注意: 服务器端也需要正确配置 CORS 头部,允许接收凭证。
第四章:Axios 在实际项目中的最佳实践
掌握了 Axios 的核心功能,下一步就是如何在实际项目中将其运用得炉火纯青,构建出健壮、可维护的 API 请求模块。
4.1 统一 API 管理:封装请求服务模块
将各个业务模块的 API 接口封装成独立的函数或模块,统一管理请求 URL、参数和特定逻辑。这样可以提高代码的可读性、可维护性和复用性。
目录结构示例:
src
├── api
│ ├── index.js // 统一导出所有 api 模块
│ ├── user.js // 用户相关 api
│ ├── product.js // 商品相关 api
│ └── order.js // 订单相关 api
└── utils
└── request.js // Axios 封装及拦截器配置
src/utils/request.js (Axios 核心封装)
“`javascript
import axios from ‘axios’;
import { ElMessage, ElLoading } from ‘element-plus’; // 假设使用 Element Plus UI 库
import router from ‘@/router’; // 假设有 Vue Router
// 1. 创建 Axios 实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API || ‘/api’, // 根据环境变量配置基础 URL
timeout: 10000, // 请求超时时间
withCredentials: true // 允许携带 Cookie
});
let loadingInstance = null; // 用于存储 Loading 实例
let requestCount = 0; // 记录正在进行的请求数量
function showLoading() {
if (requestCount === 0) { // 只有在没有请求时才显示 Loading
loadingInstance = ElLoading.service({
lock: true,
text: ‘加载中…’,
spinner: ‘el-icon-loading’,
background: ‘rgba(0, 0, 0, 0.7)’
});
}
requestCount++;
}
function hideLoading() {
requestCount–;
if (requestCount <= 0) { // 所有请求都结束时隐藏 Loading
loadingInstance.close();
requestCount = 0; // 确保计数器不会出现负数
}
}
// 2. 请求拦截器
service.interceptors.request.use(
config => {
showLoading(); // 显示 Loading
// 添加认证 Token
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// 可以统一处理请求参数,例如将所有 GET 请求的数组参数转换为逗号分隔字符串
// if (config.method === 'get' && config.params) {
// for (let key in config.params) {
// if (Array.isArray(config.params[key])) {
// config.params[key] = config.params[key].join(',');
// }
// }
// }
return config;
},
error => {
hideLoading(); // 请求失败也要隐藏 Loading
console.error('请求发送错误:', error);
return Promise.reject(error);
}
);
// 3. 响应拦截器
service.interceptors.response.use(
response => {
hideLoading(); // 隐藏 Loading
// 后端通常会返回一个统一的数据结构,例如 { code: 0, message: '成功', data: {} }
const { code, message, data } = response.data;
if (code === 0) { // 业务成功
return data; // 直接返回数据部分,简化组件调用
} else {
// 业务失败
ElMessage.error(message || '操作失败');
return Promise.reject(new Error(message || 'Error'));
}
},
error => {
hideLoading(); // 响应错误也要隐藏 Loading
console.error('响应错误:', error);
// HTTP 状态码错误处理
if (error.response) {
const { status, data } = error.response;
let errorMessage = data.message || '未知错误';
switch (status) {
case 400: errorMessage = '请求错误(400)'; break;
case 401:
errorMessage = '未授权,请重新登录';
localStorage.removeItem('token'); // 清除 Token
router.push('/login'); // 跳转到登录页
break;
case 403: errorMessage = '拒绝访问(403)'; break;
case 404: errorMessage = `请求地址出错: ${error.response.config.url}`; break;
case 408: errorMessage = '请求超时(408)'; break;
case 500: errorMessage = '服务器内部错误(500)'; break;
case 501: errorMessage = '服务未实现(501)'; break;
case 502: errorMessage = '网络错误(502)'; break;
case 503: errorMessage = '服务不可用(503)'; break;
case 504: errorMessage = '网络超时(504)'; break;
case 505: errorMessage = 'HTTP版本不受支持(505)'; break;
default: errorMessage = `连接错误: ${status}`;
}
ElMessage.error(errorMessage);
} else if (error.request) {
// 请求已发出但没有收到响应
if (error.code === 'ECONNABORTED' && error.message.includes('timeout')) {
ElMessage.error('请求超时,请检查网络或稍后重试');
} else {
ElMessage.error('网络连接异常,请检查网络或稍后重试');
}
} else {
// 在设置请求时发生了错误
ElMessage.error(`请求配置错误: ${error.message}`);
}
return Promise.reject(error);
}
);
export default service;
“`
src/api/user.js (用户 API 模块)
“`javascript
import request from ‘@/utils/request’; // 引入封装好的 axios 实例
export function login(data) {
return request({
url: ‘/user/login’,
method: ‘post’,
data
});
}
export function getUserInfo(params) {
return request({
url: ‘/user/info’,
method: ‘get’,
params
});
}
export function logout() {
return request({
url: ‘/user/logout’,
method: ‘post’
});
}
“`
src/api/index.js (统一导出)
“`javascript
import * as user from ‘./user’;
import * as product from ‘./product’;
// … 更多 api 模块
export default {
user,
product,
// …
};
“`
组件中使用:
“`javascript
import api from ‘@/api’; // 引入统一导出的 api 模块
export default {
methods: {
async handleLogin() {
try {
const response = await api.user.login({ username: ‘test’, password: ‘123’ });
console.log(‘登录成功:’, response); // response 就是后端的 data 部分
localStorage.setItem(‘token’, response.token);
// router.push(‘/dashboard’);
} catch (error) {
console.error(‘登录失败 (组件内捕获):’, error.message);
// 这里的错误信息已经在拦截器中 ElMessage 提示过了
// 如果需要特殊处理,可以在这里进行
}
},
async fetchUserInfo() {
try {
const userInfo = await api.user.getUserInfo();
console.log(‘用户信息:’, userInfo);
} catch (error) {
console.error(‘获取用户信息失败:’, error.message);
}
}
}
};
“`
通过这种封装,组件中调用 API 变得非常简洁,无需关心请求头、错误处理、Loading 状态等底层细节。
4.2 Loading 状态管理
在 request.js 中,我们通过 requestCount 变量和 ElLoading 实现了全局 Loading。
* showLoading():在请求拦截器中调用,每次请求发出时 requestCount 加一。当 requestCount 从 0 变为 1 时,显示 Loading。
* hideLoading():在响应拦截器(成功或失败)中调用,每次响应返回时 requestCount 减一。当 requestCount 减到 0 时,隐藏 Loading。
这种机制确保了即使有多个并发请求,Loading 也能正确显示和隐藏,避免了“闪烁”或过早消失的问题。
4.3 Token 管理与刷新
在请求拦截器中,我们统一为请求头添加了 Authorization。
在响应拦截器中,对于 401(未授权)错误,我们清除了本地 Token 并重定向到登录页。
更复杂的场景可能涉及 Token 刷新:当后端返回特定状态码(如 401)表示 Token 过期但可以刷新时,可以在响应拦截器中:
1. 暂停当前所有待处理的请求。
2. 发起刷新 Token 的请求。
3. 如果刷新成功,更新 Token,并用新的 Token 重试之前暂停的所有请求。
4. 如果刷新失败,则重定向到登录页。
这需要一个“请求队列”和“刷新锁”机制,在大型应用中非常实用,但实现会更复杂,超出了本文的初衷,但 Axios 的拦截器提供了实现这种机制的强大基础。
4.4 请求取消策略
除了前面提到的单个请求取消,在实际项目中,可以结合路由守卫或组件生命周期钩子,实现批量取消请求。
* 路由切换时取消请求:在路由切换前,取消所有当前页面正在进行中的请求。
* 组件卸载时取消请求:在组件的 beforeDestroy/onUnmounted 钩子中,取消该组件发出的所有请求。
这需要一个集中管理 AbortController 实例的机制。例如,可以创建一个 Map 来存储每个请求的 AbortController,并在需要时调用 abort()。
4.5 环境区分
在 request.js 中,我们使用了 process.env.VUE_APP_BASE_API 来设置 baseURL。
在 Vue CLI 或 Create React App 等构建工具中,可以根据 NODE_ENV(开发、测试、生产)设置不同的环境变量,从而在不同环境下自动使用不同的 API 地址。
例如,在 .env.development 文件中:
VUE_APP_BASE_API = /dev-api
在 .env.production 文件中:
VUE_APP_BASE_API = https://api.your-prod-domain.com
结论:告别复杂,拥抱高效
通过本文的详细阐述,我们可以清晰地看到,Axios 绝不仅仅是一个简单的 HTTP 请求库,它是一个功能完备、高度可配置的请求管理系统。
- 它将原生 XHR 的复杂性抽象为简洁的 Promise API。
- 它弥补了 Fetch API 在拦截器、取消、超时等高级功能上的不足。
- 它的拦截器机制为全局性的请求前处理和响应后处理提供了无限可能,使得统一鉴权、错误处理、Loading 状态管理变得轻而易举。
- 它的实例创建功能,让多服务、多模块的 API 管理井然有序。
- 它的取消请求、上传进度等功能,为提升用户体验和优化性能提供了坚实保障。
告别了手动管理各种请求细节的复杂性,开发者可以将更多精力聚焦于业务逻辑的实现,提升开发效率,构建出更稳定、更易维护、用户体验更佳的 Web 应用。
无论你是前端新手,还是经验丰富的资深开发者,熟练掌握并巧妙运用 Axios 的各项特性,都将是你前端工具箱中不可或缺的利器。现在,就开始你的 Axios 之旅,让前端 API 请求管理变得前所未有的高效和优雅吧!