在 Vue 项目开发中,网络请求是核心环节之一。Axios 作为一款功能强大的 HTTP 客户端,是 Vue 生态中最常用的请求工具,但直接在业务代码中零散使用 Axios 会导致代码冗余、维护困难,且难以统一处理请求 / 响应拦截、错误捕获等通用逻辑。本文将详细讲解如何优雅地封装 Axios,结合 Vue 实现请求拦截、响应拦截、统一错误处理,让网络请求层更规范、可维护。
一、前置准备:安装 Axios
首先在 Vue 项目中安装 Axios(适用于 Vue2/Vue3):
# npm npm install axios --save # yarn yarn add axios # pnpm pnpm add axios二、核心封装:创建 Axios 实例
我们先创建一个独立的请求封装文件(如src/utils/request.js),通过创建 Axios 实例而非直接使用全局 Axios,可避免不同请求配置相互污染,同时便于定制化配置。
基础配置:创建实例
import axios from 'axios' // 创建Axios实例 const service = axios.create({ // 基础请求地址(可通过环境变量配置,区分开发/生产环境) baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // 请求超时时间 timeout: 10000, // 请求头默认配置 headers: { 'Content-Type': 'application/json;charset=utf-8' } }) export default service环境变量配置建议:在 Vue3+Vite 项目中,创建.env.development和.env.production文件区分环境:
# .env.development VITE_API_BASE_URL = 'http://localhost:3000/api' # .env.production VITE_API_BASE_URL = 'https://prod-api.example.com/api'三、拦截器:统一处理请求 / 响应
Axios 的拦截器分为请求拦截器(request)和响应拦截器(response),可在请求发送前、响应返回后执行通用逻辑,是封装的核心。
1. 请求拦截器:添加 Token、处理请求参数
请求拦截器常用于:
- 给请求头添加认证 Token(如 JWT);
- 统一处理请求参数(如序列化、添加公共参数);
- 显示加载中状态(如 Loading 弹窗)。
// 续接上面的request.js import { ElMessage } from 'element-plus' // Vue3+Element Plus,Vue2可改用Element UI import { useUserStore } from '@/stores/user' // Pinia存储Token,Vue2可改用Vuex // 请求拦截器 service.interceptors.request.use( (config) => { // 1. 添加认证Token const userStore = useUserStore() if (userStore.token) { config.headers.Authorization = `Bearer ${userStore.token}` } // 2. GET请求参数序列化(可选) if (config.method === 'get' && config.params) { // 可自定义参数处理逻辑,如过滤空值 config.params = Object.fromEntries( Object.entries(config.params).filter(([_, v]) => v !== undefined && v !== null && v !== '') ) } // 3. 显示加载中(示例) // loadingInstance = ElLoading.service({ text: '请求中...' }) return config }, (error) => { // 请求发送失败时的处理 // loadingInstance?.close() // 关闭加载 ElMessage.error('请求发送失败,请检查网络') return Promise.reject(error) } )2. 响应拦截器:统一处理响应、捕获业务错误
响应拦截器常用于:
- 统一解析响应数据(剥离外层包装,只返回业务数据);
- 处理 HTTP 状态码错误(如 401、403、500);
- 关闭加载中状态;
- 统一提示业务错误。
// 续接request.js service.interceptors.response.use( (response) => { // 关闭加载中 // loadingInstance?.close() // 假设后端返回格式:{ code: 200, data: {}, msg: '成功' } const { code, data, msg } = response.data // 业务成功 if (code === 200) { return data // 只返回核心数据,简化业务代码 } // 业务失败(如参数错误、权限不足等) ElMessage.error(msg || '请求失败') return Promise.reject(new Error(msg || '请求失败')) }, (error) => { // 关闭加载中 // loadingInstance?.close() // 处理HTTP状态码错误 const status = error.response?.status let errorMsg = '网络异常,请稍后重试' switch (status) { case 401: errorMsg = '登录失效,请重新登录' // 清除Token并跳转到登录页 const userStore = useUserStore() userStore.clearToken() window.location.href = '/login' break case 403: errorMsg = '暂无权限访问该资源' break case 404: errorMsg = '请求的资源不存在' break case 500: errorMsg = '服务器内部错误' break default: errorMsg = error.response?.data?.msg || errorMsg } ElMessage.error(errorMsg) return Promise.reject(error) } )四、业务请求封装:按模块分类
封装好基础请求实例后,建议按业务模块(如用户、商品、订单)创建请求文件,让代码结构更清晰。
示例:src/api/user.js
import request from '@/utils/request' /** * 用户登录 * @param {Object} data - 登录参数(username/password) */ export const login = (data) => { return request({ url: '/user/login', method: 'post', data }) } /** * 获取用户信息 */ export const getUserInfo = () => { return request({ url: '/user/info', method: 'get' }) } /** * 修改用户信息 * @param {Object} data - 用户信息 */ export const updateUserInfo = (data) => { return request({ url: '/user/info', method: 'put', data }) }五、在 Vue 组件中使用
封装完成后,在 Vue 组件中调用请求会非常简洁,且错误处理已统一封装,无需重复编写。
Vue3 组合式 API 示例
<template> <div> <el-button @click="getUserInfo">获取用户信息</el-button> </div> </template> <script setup> import { getUserInfo } from '@/api/user' import { ElMessage } from 'element-plus' const getUserInfo = async () => { try { // 直接获取核心数据,无需解析code/data const data = await getUserInfo() console.log('用户信息:', data) ElMessage.success('获取用户信息成功') } catch (error) { // 可选:特殊场景下自定义错误处理(通用错误已在拦截器处理) console.error('获取用户信息失败:', error) } } </script>Vue2 选项式 API 示例
<template> <div> <el-button @click="getUserInfo">获取用户信息</el-button> </div> </template> <script> import { getUserInfo } from '@/api/user' import { Message } from 'element-ui' export default { methods: { async getUserInfo() { try { const data = await getUserInfo() console.log('用户信息:', data) Message.success('获取用户信息成功') } catch (error) { console.error('获取用户信息失败:', error) } } } } </script>六、进阶优化:请求取消、重复请求拦截
1. 取消重复请求
避免短时间内重复发送相同请求(如多次点击提交按钮):
// 在request.js中添加 const pendingRequests = new Map() // 存储待处理的请求 // 生成请求唯一标识 const generateRequestKey = (config) => { return `${config.method}-${config.url}-${JSON.stringify(config.params || config.data)}` } // 添加请求到待处理列表 const addPendingRequest = (config) => { const key = generateRequestKey(config) config.cancelToken = new axios.CancelToken((cancel) => { if (!pendingRequests.has(key)) { pendingRequests.set(key, cancel) } }) } // 取消重复请求 const cancelPendingRequest = (config) => { const key = generateRequestKey(config) if (pendingRequests.has(key)) { const cancel = pendingRequests.get(key) cancel('重复请求已取消') pendingRequests.delete(key) } } // 在请求拦截器中添加 service.interceptors.request.use( (config) => { // 取消重复请求 cancelPendingRequest(config) // 添加当前请求到待处理列表 addPendingRequest(config) // 原有逻辑... return config }, (error) => { // 原有逻辑... } ) // 在响应拦截器中移除已完成的请求 service.interceptors.response.use( (response) => { cancelPendingRequest(response.config) // 原有逻辑... }, (error) => { cancelPendingRequest(error.config) // 原有逻辑... } )2. 自定义请求配置
支持业务请求覆盖默认配置(如超时时间、响应类型):
// 示例:上传文件请求(修改Content-Type) export const uploadFile = (data) => { return request({ url: '/upload', method: 'post', data, headers: { 'Content-Type': 'multipart/form-data' }, timeout: 30000 // 上传文件超时时间延长至30秒 }) }七、总结
通过以上封装,我们实现了 Vue 与 Axios 的优雅集成,核心优势如下:
- 代码复用:统一的请求 / 响应拦截逻辑,避免业务代码冗余;
- 易于维护:按模块拆分请求,配置集中管理,修改更便捷;
- 用户体验:统一的加载状态、错误提示,提升交互一致性;
- 健壮性:处理了 Token 过期、重复请求、网络异常等边界场景;
- 简洁易用:业务代码只需关注核心逻辑,无需重复解析响应、捕获错误。
这套封装方案适配 Vue2 和 Vue3,可根据项目实际需求(如 UI 框架、状态管理工具)灵活调整,是 Vue 项目中网络请求层的通用最佳实践。