1. 为什么需要深度封装Axios?
在Vue或React项目中直接使用原生Axios就像用瑞士军刀切牛排——功能都有但用起来别扭。我经历过一个电商后台项目,初期每个开发者随意创建axios实例,导致:
- 重复代码:每个文件都写一遍
baseURL和timeout - 混乱的拦截逻辑:登录拦截有的写在请求前,有的写在响应后
- 文件上传各显神通:有的用FormData,有的直接传二进制
- 接口定义散落各处:难以维护的接口URL字典
深度封装的核心目标是建立企业级HTTP请求规范。通过这次封装,我们将实现:
- 统一拦截器管理(请求/响应/错误)
- 标准化文件处理流程
- 业务接口集中配置
- 类型安全的API调用
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基础封装:创建增强型Axios实例
2.1 初始化核心配置
首先创建src/utils/http.js作为封装入口:
javascript复制import axios from 'axios'
import { message } from 'antd'
// 创建不同环境的baseURL映射
const envMap = {
development: 'http://dev-api.example.com',
production: 'https://api.example.com',
test: 'http://test-api.example.com'
}
// 初始化实例配置
const instance = axios.create({
baseURL: envMap[process.env.NODE_ENV],
timeout: 15000,
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
关键细节:环境变量判断要放在封装层,避免业务代码中硬编码URL。这里使用
process.env.NODE_ENV自动匹配环境。
2.2 实现智能Content-Type切换
传统做法是固定设置Content-Type,但实际需要根据数据类型动态调整:
javascript复制// 在instance拦截器中添加请求头处理
instance.interceptors.request.use(config => {
if (config.data instanceof FormData) {
config.headers['Content-Type'] = 'multipart/form-data'
} else if (typeof config.data === 'string') {
config.headers['Content-Type'] = 'text/plain'
}
return config
})
实测中发现,当上传文件时如果忘记手动设置FormData,会导致服务端接收失败。这个自动切换机制能避免这类低级错误。
3. 拦截器深度开发实战
3.1 请求拦截器:统一身份认证
javascript复制instance.interceptors.request.use(
config => {
// 从store或localStorage获取token
const token = localStorage.getItem('ACCESS_TOKEN')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
// 特殊接口跳过拦截
if (config.url.includes('/public/')) {
delete config.headers.Authorization
}
return config
},
error => {
return Promise.reject(error)
}
)
踩坑记录:曾经在SSR项目直接访问localStorage导致报错,后来改为从cookie获取。建议封装getToken方法统一处理不同环境的凭证获取。
3.2 响应拦截器:错误分级处理
javascript复制instance.interceptors.response.use(
response => {
// 二进制流直接返回(用于文件下载)
if (response.config.responseType === 'blob') {
return response.data
}
const { code, data, message: msg } = response.data
if (code === 200) {
return data
} else {
return handleError(code, msg)
}
},
error => {
// 网络错误处理
if (!error.response) {
return handleError('NETWORK_ERROR', '网络连接异常')
}
const status = error.response.status
switch (status) {
case 401:
return handleError(status, '请重新登录')
case 403:
return handleError(status, '没有操作权限')
case 500:
return handleError(status, '服务器内部错误')
default:
return handleError(status, error.response.data.message)
}
}
)
// 统一错误处理器
function handleError(code, msg) {
// 开发环境打印完整错误
if (process.env.NODE_ENV === 'development') {
console.error(`[${code}] ${msg}`)
}
// 根据业务需求弹出提示或跳转登录页
message.error(msg)
return Promise.reject({ code, message: msg })
}
经验分享:错误处理要区分开发和生产环境。我们团队约定:
- 开发环境:显示完整错误堆栈
- 生产环境:友好提示+错误码记录
4. 文件处理专业方案
4.1 文件上传封装
javascript复制/**
* 文件上传方法
* @param {string} url 接口地址
* @param {File} file 文件对象
* @param {object} extraData 额外表单数据
* @param {function} onProgress 进度回调
*/
export function uploadFile(url, file, extraData = {}, onProgress) {
const formData = new FormData()
formData.append('file', file)
// 添加额外参数
Object.keys(extraData).forEach(key => {
formData.append(key, extraData[key])
})
return instance.post(url, formData, {
onUploadProgress: progressEvent => {
if (onProgress) {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
)
onProgress(percent)
}
}
})
}
重要细节:FormData字段名需要与后端约定一致。曾经因为字段名大小写不一致导致上传失败。
4.2 文件下载与Blob处理
javascript复制/**
* 文件下载方法
* @param {string} url 下载地址
* @param {string} filename 保存文件名
*/
export function downloadFile(url, filename) {
return instance({
url,
method: 'GET',
responseType: 'blob'
}).then(blob => {
// 创建临时a标签触发下载
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
})
}
实测问题:IE浏览器需要特殊处理,建议增加如下兼容代码:
javascript复制// IE兼容方案
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob, filename)
} else {
// 标准方案...
}
5. 业务接口统一管理
5.1 接口模块化设计
创建src/api目录,按业务模块划分:
code复制api/
├── auth.js # 认证相关接口
├── user.js # 用户管理
├── product.js # 商品管理
└── index.js # 统一导出
示例用户模块:
javascript复制// api/user.js
import http from '../utils/http'
export default {
// 获取用户列表
getUsers(params) {
return http.get('/users', { params })
},
// 创建用户
createUser(data) {
return http.post('/users', data)
},
// 批量删除
batchDelete(ids) {
return http.delete('/users', { data: { ids } })
}
}
5.2 TypeScript增强支持
对大型项目建议添加类型定义:
typescript复制// types/api.d.ts
declare module '@/api' {
interface User {
id: number
name: string
avatar: string
}
export interface IUserAPI {
getUsers(params?: PaginationParams): Promise<PaginatedList<User>>
createUser(data: UserCreateDTO): Promise<User>
batchDelete(ids: number[]): Promise<void>
}
}
// 在vue组件中使用
import { IUserAPI } from '@/api'
const userAPI: IUserAPI = inject('userAPI')
类型提示能显著减少接口调用错误,特别是在参数复杂时效果更明显。
6. 高级封装技巧
6.1 请求取消与防抖
搜索框等高频请求场景需要防抖:
javascript复制let cancelToken = null
export function searchProducts(keyword) {
// 取消之前的请求
if (cancelToken) {
cancelToken.cancel('Operation canceled due to new request')
}
// 创建新的取消令牌
cancelToken = axios.CancelToken.source()
return instance.get('/products/search', {
params: { keyword },
cancelToken: cancelToken.token
})
}
6.2 缓存策略实现
javascript复制const cacheMap = new Map()
export function getProductDetail(id, forceRefresh = false) {
// 强制刷新或没有缓存时重新请求
if (forceRefresh || !cacheMap.has(id)) {
const promise = instance.get(`/products/${id}`)
cacheMap.set(id, promise)
return promise
}
return cacheMap.get(id)
}
缓存策略需要根据业务特点调整,比如:
- 高频读取数据:适合缓存
- 实时性要求高:禁用缓存
- 大数据量:使用内存缓存+本地存储
6.3 性能监控集成
javascript复制instance.interceptors.request.use(config => {
if (process.env.NODE_ENV === 'development') {
config.metadata = { startTime: performance.now() }
}
return config
})
instance.interceptors.response.use(response => {
if (process.env.NODE_ENV === 'development') {
const duration = performance.now() - response.config.metadata.startTime
console.log(`[API Perf] ${response.config.url}: ${duration.toFixed(2)}ms`)
}
return response
})
这个简单的性能监控能帮助发现慢接口,我们团队实践发现,超过500ms的接口就需要优化。
7. 完整代码架构
最终项目结构建议:
code复制src/
├── api/ # 业务接口
│ ├── module1.js
│ ├── module2.js
│ └── index.js
├── utils/
│ ├── http.js # Axios封装核心
│ ├── interceptors.js # 拦截器定义
│ └── file.js # 文件处理
└── types/
└── api.d.ts # 接口类型定义
在main.js中的初始化示例:
javascript复制import { initHttp } from '@/utils/http'
import api from '@/api'
const http = initHttp({
baseURL: import.meta.env.VITE_API_URL,
errorHandler: (code, msg) => {
// 自定义全局错误处理
}
})
app.provide('http', http)
app.provide('api', api)
这种架构下,组件中只需通过依赖注入使用:
javascript复制export default {
inject: ['api'],
methods: {
async loadUsers() {
try {
this.users = await this.api.user.getUsers()
} catch (err) {
console.error(err)
}
}
}
}
经过多个项目的实践检验,这套封装方案能显著提升开发效率和代码质量。特别是在大型项目中,统一的请求管理能减少至少30%的重复代码量。
