别再复制粘贴了!手把手教你用TypeScript封装一个企业级axios请求库(Vue3 + Vite环境)

张开发
2026/4/3 21:28:03 15 分钟阅读
别再复制粘贴了!手把手教你用TypeScript封装一个企业级axios请求库(Vue3 + Vite环境)
别再复制粘贴了手把手教你用TypeScript封装一个企业级axios请求库Vue3 Vite环境每次新开Vue3项目都要重新配置axios还在为重复处理401错误和Token刷新头疼今天我们就来彻底解决这个问题。不同于网上那些基础教程本文将带你从工程化角度构建一个真正适合企业级项目的TypeScript axios封装方案。这个方案不仅包含类型安全、自动Token注入、统一错误处理还会教你如何处理请求取消、文件上传进度监控等高级功能。1. 为什么需要企业级axios封装在真实项目中我们经常会遇到以下痛点每个接口都要手动添加Token容易遗漏错误处理分散在各个组件难以维护缺乏类型提示调用时像开盲盒重复处理加载状态和取消请求不同环境API地址切换麻烦我们的目标是创建一个http.ts工具让团队所有成员都能这样优雅地调用APIconst { data } await http.getUser[](/api/users, { params: { page: 1 }, cancelKey: user-list // 可取消的请求 })2. 基础封装类型安全与拦截器2.1 初始化axios实例首先创建src/utils/http.ts文件import axios, { AxiosRequestConfig, AxiosResponse } from axios // 定义基础响应类型 interface BaseResponseT any { code: number data: T message?: string } // 创建axios实例 const instance axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 10000, headers: { Content-Type: application/json } })2.2 增强类型定义为了让我们的封装保持TypeScript的强大类型推断需要扩展axios类型declare module axios { interface AxiosRequestConfig { // 自定义配置项 silent?: boolean // 是否静默处理错误 cancelKey?: string // 请求取消的key } interface AxiosResponseT any { // 扩展响应类型 success: boolean message?: string } }2.3 拦截器实现请求拦截器负责自动注入Tokeninstance.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config })响应拦截器处理通用逻辑instance.interceptors.response.use( (response: AxiosResponseBaseResponse) { if (response.data.code ! 0) { return Promise.reject(response.data) } return response.data.data }, error { // 统一错误处理 if (error.response?.status 401) { // Token过期处理 } return Promise.reject(error) } )3. 高级功能实现3.1 请求取消功能在大型应用中我们需要避免组件卸载后仍然执行setState操作const cancelTokenMap new Mapstring, Canceler() function addCancelToken(config: AxiosRequestConfig) { if (config.cancelKey) { config.cancelToken new axios.CancelToken(cancel { cancelTokenMap.set(config.cancelKey!, cancel) }) } } function removeCancelToken(config: AxiosRequestConfig) { if (config.cancelKey) { cancelTokenMap.delete(config.cancelKey!) } } // 在拦截器中调用 instance.interceptors.request.use(config { addCancelToken(config) return config }) // 导出取消方法 export function cancelRequest(key: string) { if (cancelTokenMap.has(key)) { cancelTokenMap.get(key)!() cancelTokenMap.delete(key) } }3.2 文件上传进度监控对于大文件上传进度反馈很重要export function uploadFile( url: string, file: File, onProgress?: (percent: number) void ) { const formData new FormData() formData.append(file, file) return instance.post(url, formData, { headers: { Content-Type: multipart/form-data }, onUploadProgress: progressEvent { if (progressEvent.total onProgress) { const percent Math.round( (progressEvent.loaded * 100) / progressEvent.total ) onProgress(percent) } } }) }4. 完整封装与使用示例4.1 最终封装代码// src/utils/http.ts import axios, { AxiosRequestConfig, Canceler } from axios // 类型定义和拦截器实现... // 高级功能实现... // 封装核心请求方法 const http { getT any(url: string, config?: AxiosRequestConfig) { return instance.getT(url, config) }, postT any(url: string, data?: any, config?: AxiosRequestConfig) { return instance.postT(url, data, config) }, // 其他方法... cancel: cancelRequest, upload: uploadFile } export default http4.2 在Vue组件中使用script setup langts import { ref } from vue import http from /utils/http const data ref() const loading ref(false) const fetchData async () { try { loading.value true data.value await http.get(/api/data, { cancelKey: data-fetch }) } catch (err) { if (!axios.isCancel(err)) { // 处理真实错误 } } finally { loading.value false } } // 组件卸载时取消请求 onUnmounted(() { http.cancel(data-fetch) }) /script5. 工程化最佳实践5.1 环境变量配置在.env文件中VITE_API_BASE_URLhttps://api.yourdomain.com VITE_UPLOAD_URL/upload5.2 API模块化组织建议按功能模块组织API// src/api/user.ts import http from /utils/http export function getUserList(params: { page: number }) { return http.getUser[](/users, { params }) } export function updateUser(id: number, data: User) { return http.put(/users/${id}, data) }5.3 单元测试要点使用vitest测试关键功能import { describe, it, expect, vi } from vitest import http from /utils/http describe(http工具, () { it(应该自动添加Token, async () { localStorage.setItem(token, test-token) const config await http.interceptors.request.handlers[0].fulfilled!({}) expect(config.headers.Authorization).toBe(Bearer test-token) }) })6. 性能优化与调试技巧6.1 请求缓存实现对于不常变的数据可以添加缓存层const cache new Mapstring, { expire: number; data: any }() async function getWithCacheT(url: string, ttl 30000) { const cached cache.get(url) if (cached cached.expire Date.now()) { return cached.data as T } const data await http.getT(url) cache.set(url, { data, expire: Date.now() ttl }) return data }6.2 开发环境调试在vite.config.ts中添加代理server: { proxy: { /api: { target: http://localhost:3000, changeOrigin: true } } }6.3 性能监控添加请求耗时统计instance.interceptors.request.use(config { config.metadata { startTime: Date.now() } return config }) instance.interceptors.response.use(response { const duration Date.now() - response.config.metadata.startTime console.log(请求 ${response.config.url} 耗时 ${duration}ms) return response })

更多文章