当前位置: 首页 > news >正文

Vue3中优雅封装axios的三种进阶实践

1. 为什么需要封装axios?

在Vue3项目中使用axios发送HTTP请求时,直接裸用axios虽然简单,但随着项目规模扩大,会遇到几个典型问题。首先是代码重复,每个请求都要写完整的配置;其次是难以统一管理请求和响应拦截逻辑;最后是类型提示缺失,维护成本高。这些问题都会影响开发效率和代码质量。

我接手过一个电商后台项目,初期图省事直接使用axios,结果发现几十个页面都在重复写baseURL配置和错误处理逻辑。后来接口域名变更时,光修改baseURL就花了半天时间。这种经历让我深刻认识到封装axios的必要性。

封装axios的核心价值在于:

  • 统一配置:集中管理baseURL、超时时间等公共参数
  • 拦截器管理:统一处理token注入、错误提示等逻辑
  • 简化调用:封装常用方法,减少重复代码
  • 类型安全:通过TypeScript增强代码提示和校验

2. 基础封装方案

2.1 创建axios实例

先看最基本的封装方式,创建一个request.ts文件:

import axios from 'axios' const service = axios.create({ baseURL: import.meta.env.VITE_API_URL, timeout: 10000, headers: { 'Content-Type': 'application/json' } }) // 请求拦截器 service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) // 响应拦截器 service.interceptors.response.use( response => response.data, error => { if (error.response?.status === 401) { // 处理token过期 } return Promise.reject(error) } ) export default service

这种封装虽然简单,但已经解决了80%的常见需求。使用时直接引入:

import request from '@/utils/request' request.get('/user/info')

2.2 添加TypeScript支持

为了让封装更完善,我们需要添加类型支持。先定义基础响应类型:

interface BaseResponse<T = any> { code: number data: T message: string }

然后改造axios实例:

const service = axios.create({ // ...原有配置 }) as AxiosInstance export default { get<T = any>(url: string, config?: AxiosRequestConfig): Promise<BaseResponse<T>> { return service.get(url, config) }, // 其他方法同理 }

这样调用时就能获得完善的类型提示了:

const res = await request.get<{ name: string }>('/user/info') console.log(res.data.name) // 有类型提示

3. 进阶封装方案

3.1 使用provide/inject实现依赖注入

在大型项目中,我们可能需要多个axios实例(比如对接不同后端服务)。Vue3的provide/inject特性非常适合这种场景。

首先在main.ts中提供实例:

import { createApp } from 'vue' import App from './App.vue' import { createAxios } from './utils/request' const app = createApp(App) // 主API实例 app.provide('mainAxios', createAxios({ baseURL: import.meta.env.VITE_MAIN_API })) // 支付API实例 app.provide('payAxios', createAxios({ baseURL: import.meta.env.VITE_PAY_API })) app.mount('#app')

在组件中使用:

import { inject } from 'vue' const mainAxios = inject('mainAxios') const payAxios = inject('payAxios') // 使用方式与普通axios一致 mainAxios.get('/user') payAxios.post('/order')

这种方式的优势在于:

  • 实现多实例管理
  • 避免全局污染
  • 便于测试(可以轻松替换mock实例)

3.2 基于Composition API的工厂函数

对于更复杂的场景,我们可以创建axios工厂函数:

export function useAxios(baseOptions: AxiosRequestConfig) { const instance = axios.create(baseOptions) // 添加拦截器 instance.interceptors.request.use(config => { // 自定义逻辑 return config }) // 封装常用方法 const get = <T>(url: string, config?: AxiosRequestConfig) => { return instance.get<T>(url, config) } return { instance, get, // 其他方法... } }

在组件中使用:

import { useAxios } from '@/composables/useAxios' const { get } = useAxios({ baseURL: '/api' }) const data = await get<User[]>('/users')

工厂函数模式特别适合:

  • 需要动态配置的场景
  • 需要复用封装逻辑的组件
  • 需要与其他Composition API组合使用

4. 高级技巧与实践

4.1 拦截器链式控制

复杂项目可能需要根据不同条件应用不同拦截器。我们可以实现拦截器管理器:

class InterceptorManager { private instance: AxiosInstance private interceptors = { request: [] as number[], response: [] as number[] } constructor(instance: AxiosInstance) { this.instance = instance } addRequestInterceptor(interceptor: AxiosInterceptor) { const id = this.instance.interceptors.request.use( interceptor.onFulfilled, interceptor.onRejected ) this.interceptors.request.push(id) return id } eject(id: number, type: 'request' | 'response') { this.instance.interceptors[type].eject(id) } }

使用示例:

const manager = new InterceptorManager(axiosInstance) // 添加拦截器 const id = manager.addRequestInterceptor({ onFulfilled: config => { // 特殊处理逻辑 return config } }) // 移除拦截器 manager.eject(id, 'request')

4.2 请求取消与竞态控制

处理频繁触发的请求时(如搜索框),需要取消之前的请求:

const pendingMap = new Map() function addPending(config: AxiosRequestConfig) { const key = `${config.method}-${config.url}` const controller = new AbortController() config.signal = controller.signal if (pendingMap.has(key)) { pendingMap.get(key).abort() } pendingMap.set(key, controller) } function removePending(config: AxiosRequestConfig) { const key = `${config.method}-${config.url}` if (pendingMap.has(key)) { pendingMap.delete(key) } } // 在拦截器中调用 service.interceptors.request.use(config => { addPending(config) return config }) service.interceptors.response.use(response => { removePending(response.config) return response })

4.3 类型安全的极致实践

通过泛型和类型推导,可以实现更强大的类型安全:

type ApiMethod = 'get' | 'post' | 'put' | 'delete' interface ApiConfig<T = any> { url: string method: ApiMethod data?: T } function createApi<Req, Res>(config: ApiConfig<Req>) { return (data?: Req) => { return service<Res>({ ...config, data }) } } // 定义API const getUser = createApi<{ id: string }, { name: string }>({ url: '/user', method: 'get' }) // 调用时会有完整类型提示 const res = await getUser({ id: '123' }) console.log(res.name) // 正确推断出string类型

5. 实战中的经验分享

在实际项目中封装axios时,我踩过几个坑值得注意。首先是拦截器的执行顺序问题,后添加的拦截器会先执行,这与中间件模式相反。有次调试时发现token注入失效,就是因为拦截器顺序搞错了。

其次是类型扩展的问题。axios的默认类型定义比较基础,我们需要扩展AxiosRequestConfig来支持自定义配置:

declare module 'axios' { interface AxiosRequestConfig { noToken?: boolean retry?: number } }

关于错误处理,建议统一格式并区分业务错误和系统错误。我们项目中采用的格式是:

interface ErrorResult { type: 'business' | 'system' code: string message: string detail?: any }

最后是性能优化方面,对于高频接口可以添加缓存层。我们实现了一个简单的内存缓存:

const cache = new Map() function withCache(fn: Function, key: string, ttl = 3000) { return async (...args: any[]) => { const cached = cache.get(key) if (cached && Date.now() - cached.time < ttl) { return cached.data } const data = await fn(...args) cache.set(key, { data, time: Date.now() }) return data } }
http://www.jsqmd.com/news/623739/

相关文章:

  • Spring Cloud 微服务实战:构建高可用的服务注册与 API 网关系统
  • 3个终极技巧免费解锁Cursor Pro功能:完整指南与一键配置
  • Head结构改进综合实验:精度提升对比
  • Win11下Anaconda3环境变量配置引发的conda activate报错分析与解决
  • 链动 2+1” 别盲目跟风:我见过 5 家实体做崩了,核心就错在这 1 点
  • 如何免费解锁Spotify高级功能:5分钟完成广告拦截终极指南
  • 终极指南:如何用silk-v3-decoder轻松搞定音频格式转换
  • Qwen2.5-0.5B监控方案:Prometheus+Grafana部署实战
  • JavaScript屏幕API完全指南:从响应式布局到指纹采集的15种应用场景
  • 别再只用NDVI了!用GEE下载MODIS LAI数据,解锁植被分析的隐藏维度
  • 避坑指南:用MoveIt! Setup Assistant配置机械臂时必做的5个关键设置(含SRDF文件修复技巧)
  • Kali Linux下setoolkit钓鱼网站实战:从搭建到防御的完整指南
  • Hashcat实战指南:从基础到高级破解技巧
  • 为暗影精灵笔记本解锁原生性能:OmenSuperHub的纯净硬件控制方案
  • 2026年WPC门定制厂家费用揭秘,广州深圳高性价比企业推荐 - 工业推荐榜
  • 标书智能体(二)——生成标书提纲代码+提示词
  • 突破窗口限制:5分钟掌握SRWE,让任何程序窗口随心所欲调整
  • 优化Cartographer重定位速度:从子图筛选到参数调优的完整思路
  • 如何高效使用Python-Skill Bridge:专业EDA开发者的实战指南
  • STM32F103用FSMC驱动ILI9341屏幕,我踩过的那些坑和调试心得(附完整代码)
  • Coze工作流实战:我把飞书多维表格变成了一个‘智能视频内容库’
  • Teensy 4.1专用SCPI协议解析库深度解析
  • 2026年广州防火材料选型指南白皮书——合规选型场景适配安全护航 - GrowthUME
  • 三维扫描数据处理避坑指南:用Rhino7解决网格转实体的5大难题
  • WPF (进阶技巧)PasswordBox控件的安全绑定与样式美化实战
  • Shell脚本高效解析Json配置文件的3种实战方法
  • 卡内基梅隆大学:AI双模型协作其实是在“重新解题“?
  • fast-copy:企业级高性能JavaScript深度对象拷贝最佳实践
  • 速卖通关键字搜索接口实战:官方鉴权 + 分页 + 跨境商品搜索(Python 生产级实现)
  • 大模型---大模型的评测