2026/1/8 9:53:22
网站建设
项目流程
个人网站模板html5,广州app开发网站建设,青浦区网站建设,指点成金网发帖推广引言在现代前端开发中#xff0c;HTTP请求是应用与后端交互的核心环节。Axios作为目前最流行的HTTP客户端库#xff0c;以其简洁的API和强大的功能受到广泛青睐。然而#xff0c;直接在项目中裸用Axios会导致代码冗余、维护困难等问题。本文将深入探讨如何在Vue项目中全面封…引言在现代前端开发中HTTP请求是应用与后端交互的核心环节。Axios作为目前最流行的HTTP客户端库以其简洁的API和强大的功能受到广泛青睐。然而直接在项目中裸用Axios会导致代码冗余、维护困难等问题。本文将深入探讨如何在Vue项目中全面封装Axios打造企业级的HTTP请求解决方案。一、为什么需要封装Axios1.1 直接使用Axios的问题代码重复每个请求都需要写完整的配置维护困难基础配置分散在各个文件中缺乏统一错误处理难以实现请求拦截和响应拦截的统一管理类型安全缺失TypeScript项目1.2 封装带来的优势统一配置和管理提高代码复用性增强错误处理能力便于实现请求拦截、身份验证等功能提升开发效率和代码质量二、基础封装实现2.1 项目结构规划src/├── api/│ ├── index.ts # 导出所有API│ ├── request.ts # Axios封装核心│ ├── types/ # 类型定义│ │ ├── request.ts│ │ └── response.ts│ ├── modules/ # 模块化API│ │ ├── user.ts│ │ └── product.ts│ └── interceptors/ # 拦截器│ ├── request.ts│ └── response.ts2.2 创建基础请求类// src/api/request.ts import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from axios; import { RequestConfig, RequestInterceptors, CancelRequestSource } from ./types; class Request { // axios实例 instance: AxiosInstance; // 拦截器对象 interceptorsObj?: RequestInterceptors; // 存储取消请求的Map cancelRequestSource: CancelRequestSource; // 存储所有请求url requests: string[]; constructor(config: RequestConfig) { this.instance axios.create(config); this.interceptorsObj config.interceptors; this.cancelRequestSource {}; this.requests []; // 请求拦截器 this.instance.interceptors.request.use( (config: InternalAxiosRequestConfig) { // 全局请求拦截器 console.log(全局请求拦截器); // 添加取消令牌 const requestId this.generateRequestId(config); config.cancelToken new axios.CancelToken(cancel { this.cancelRequestSource[requestId] cancel; }); this.requests.push(requestId); // 自定义请求拦截器 if (this.interceptorsObj?.requestInterceptors) { config this.interceptorsObj.requestInterceptors(config); } return config; }, (error: any) { return Promise.reject(error); } ); // 使用实例拦截器 this.instance.interceptors.request.use( this.interceptorsObj?.requestInterceptors, this.interceptorsObj?.requestInterceptorsCatch ); // 响应拦截器 this.instance.interceptors.response.use( (response: AxiosResponse) { const requestId this.generateRequestId(response.config); this.removeRequest(requestId); // 全局响应拦截器 console.log(全局响应拦截器); // 自定义响应拦截器 if (this.interceptorsObj?.responseInterceptors) { response this.interceptorsObj.responseInterceptors(response); } return response.data; }, (error: any) { error.config this.removeRequest(this.generateRequestId(error.config)); // 全局错误处理 if (this.interceptorsObj?.responseInterceptorsCatch) { return this.interceptorsObj.responseInterceptorsCatch(error); } // HTTP状态码错误处理 if (error.response) { this.handleHttpError(error.response.status, error.response.data); } // 请求被取消 if (axios.isCancel(error)) { console.log(请求已被取消:, error.message); return Promise.reject(new Error(请求已被取消)); } return Promise.reject(error); } ); // 使用实例响应拦截器 this.instance.interceptors.response.use( this.interceptorsObj?.responseInterceptors, this.interceptorsObj?.responseInterceptorsCatch ); } /** * 生成请求唯一标识 */ private generateRequestId(config: AxiosRequestConfig): string { return ${config.url}-${JSON.stringify(config.params)}-${JSON.stringify(config.data)}; } /** * 移除已完成/取消的请求 */ private removeRequest(requestId: string): void { const requestIndex this.requests.indexOf(requestId); if (requestIndex -1) { this.requests.splice(requestIndex, 1); } delete this.cancelRequestSource[requestId]; } /** * 取消所有请求 */ public cancelAllRequests(): void { Object.keys(this.cancelRequestSource).forEach(requestId { this.cancelRequestSource[requestId](取消所有未完成请求); }); this.requests []; this.cancelRequestSource {}; } /** * 取消指定请求 */ public cancelRequest(requestId: string): void { if (this.cancelRequestSource[requestId]) { this.cancelRequestSource[requestId](取消请求: ${requestId}); this.removeRequest(requestId); } } /** * 处理HTTP错误 */ private handleHttpError(status: number, data: any): void { const errorMap: Recordnumber, string { 400: 请求错误, 401: 未授权请重新登录, 403: 拒绝访问, 404: 请求的资源不存在, 408: 请求超时, 500: 服务器内部错误, 501: 服务未实现, 502: 网关错误, 503: 服务不可用, 504: 网关超时, 505: HTTP版本不受支持 }; const message errorMap[status] || 连接错误${status}; console.error(HTTP错误 ${status}: ${message}, data); // 401错误跳转到登录页 if (status 401) { // router.push(/login); // 可以在这里清除用户信息 } } /** * 通用请求方法 */ requestT any(config: RequestConfig): PromiseT { return new Promise((resolve, reject) { // 单个请求的拦截器 if (config.interceptors?.requestInterceptors) { config config.interceptors.requestInterceptors(config as any); } this.instance .requestany, T(config) .then(res { // 单个响应的拦截器 if (config.interceptors?.responseInterceptors) { res config.interceptors.responseInterceptors(res); } resolve(res); }) .catch(err { reject(err); }); }); } /** * GET请求 */ getT any(config: RequestConfig): PromiseT { return this.requestT({ ...config, method: GET }); } /** * POST请求 */ postT any(config: RequestConfig): PromiseT { return this.requestT({ ...config, method: POST }); } /** * PUT请求 */ putT any(config: RequestConfig): PromiseT { return this.requestT({ ...config, method: PUT }); } /** * DELETE请求 */ deleteT any(config: RequestConfig): PromiseT { return this.requestT({ ...config, method: DELETE }); } /** * PATCH请求 */ patchT any(config: RequestConfig): PromiseT { return this.requestT({ ...config, method: PATCH }); } } export default Request;2.3 类型定义// src/api/types/request.ts import { AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from axios; export interface RequestInterceptors { // 请求拦截器 requestInterceptors?: (config: InternalAxiosRequestConfig) InternalAxiosRequestConfig; requestInterceptorsCatch?: (error: any) any; // 响应拦截器 responseInterceptors?: (response: AxiosResponse) AxiosResponse; responseInterceptorsCatch?: (error: any) any; } export interface RequestConfig extends AxiosRequestConfig { interceptors?: RequestInterceptors; // 是否显示loading showLoading?: boolean; // 是否显示错误消息 showError?: boolean; // 请求重试配置 retryConfig?: { retry: number; delay: number; }; } export interface CancelRequestSource { [index: string]: () void; } // 分页请求参数 export interface PaginationParams { page: number; pageSize: number; [key: string]: any; } // 分页响应数据 export interface PaginationResponseT any { list: T[]; total: number; page: number; pageSize: number; totalPage: number; } // 基础响应结构 export interface BaseResponseT any { code: number; data: T; message: string; success: boolean; }三、高级功能实现3.1 请求重试机制// 在request方法中添加重试逻辑 private async requestWithRetryT any( config: RequestConfig, retryCount: number 0 ): PromiseT { const maxRetry config.retryConfig?.retry || 3; const delay config.retryConfig?.delay || 1000; try { return await this.requestT(config); } catch (error) { if (retryCount maxRetry this.shouldRetry(error)) { await this.sleep(delay); return this.requestWithRetry(config, retryCount 1); } throw error; } } private shouldRetry(error: any): boolean { // 网络错误或5xx错误重试 return !error.response || error.response.status 500 || error.code ECONNABORTED; } private sleep(ms: number): Promisevoid { return new Promise(resolve setTimeout(resolve, ms)); }3.2 请求节流与防抖// 请求缓存与防抖 class RequestCache { private cache: Mapstring, { data: any; timestamp: number } new Map(); private pendingRequests: Mapstring, Promiseany new Map(); private readonly CACHE_DURATION 5 * 60 * 1000; // 5分钟 async requestT( key: string, requestFn: () PromiseT, useCache: boolean true ): PromiseT { // 检查缓存 if (useCache) { const cached this.cache.get(key); if (cached Date.now() - cached.timestamp this.CACHE_DURATION) { return cached.data; } } // 检查是否有相同请求正在处理 if (this.pendingRequests.has(key)) { return this.pendingRequests.get(key)!; } // 执行请求 const promise requestFn().then(data { if (useCache) { this.cache.set(key, { data, timestamp: Date.now() }); } this.pendingRequests.delete(key); return data; }).catch(error { this.pendingRequests.delete(key); throw error; }); this.pendingRequests.set(key, promise); return promise; } clearCache(): void { this.cache.clear(); } removeCache(key: string): void { this.cache.delete(key); } }3.3 文件上传与下载// 文件上传封装 export const uploadFile ( url: string, file: File, onProgress?: (progress: number) void ): Promiseany { const formData new FormData(); formData.append(file, file); return request({ url, method: POST, data: formData, headers: { Content-Type: multipart/form-data }, onUploadProgress: (progressEvent) { if (onProgress progressEvent.total) { const percentCompleted Math.round( (progressEvent.loaded * 100) / progressEvent.total ); onProgress(percentCompleted); } } }); }; // 文件下载封装 export const downloadFile async ( url: string, filename?: string ): Promisevoid { const response await request({ url, method: GET, responseType: blob }); const blob new Blob([response]); const downloadUrl window.URL.createObjectURL(blob); const link document.createElement(a); link.href downloadUrl; link.download filename || download; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(downloadUrl); };四、完整配置与使用4.1 创建请求实例// src/api/index.ts import Request from ./request; import type { RequestConfig } from ./types; // 环境配置 const env process.env.NODE_ENV; const baseURL env development ? http://localhost:3000/api : https://api.example.com; // 创建请求实例 const request new Request({ baseURL, timeout: 10000, headers: { Content-Type: application/json;charsetUTF-8 }, interceptors: { // 请求拦截器 requestInterceptors: (config) { // 添加token const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer ${token}; } // 添加时间戳防止缓存 if (config.method?.toUpperCase() GET) { config.params { ...config.params, _t: Date.now() }; } return config; }, // 响应拦截器 responseInterceptors: (response) { const { data } response; // 处理业务错误 if (data.code ! 200) { // 根据业务code进行错误处理 handleBusinessError(data.code, data.message); return Promise.reject(data); } return response; }, // 响应错误拦截器 responseInterceptorsCatch: (error) { // 统一错误处理 showErrorMessage(error); return Promise.reject(error); } } }); // 导出常用方法 export const http { request: T any(config: RequestConfig) request.requestT(config), get: T any(url: string, params?: any, config?: RequestConfig) request.getT({ url, params, ...config }), post: T any(url: string, data?: any, config?: RequestConfig) request.postT({ url, data, ...config }), put: T any(url: string, data?: any, config?: RequestConfig) request.putT({ url, data, ...config }), delete: T any(url: string, params?: any, config?: RequestConfig) request.deleteT({ url, params, ...config }), upload: uploadFile, download: downloadFile }; // 导出取消请求方法 export const cancelAllRequests () request.cancelAllRequests(); export const cancelRequest (requestId: string) request.cancelRequest(requestId);4.2 模块化API管理// src/api/modules/user.ts import { http } from ..; import type { BaseResponse, PaginationParams, PaginationResponse } from ../types; // 用户相关接口 export const userApi { // 登录 login: (data: { username: string; password: string }) http.postBaseResponse{ token: string }(/user/login, data), // 获取用户信息 getUserInfo: (userId: string) http.getBaseResponseUserInfo(/user/${userId}), // 分页获取用户列表 getUserList: (params: PaginationParams { keyword?: string }) http.getBaseResponsePaginationResponseUserInfo(/user/list, params), // 更新用户信息 updateUser: (userId: string, data: PartialUserInfo) http.putBaseResponsevoid(/user/${userId}, data), // 删除用户 deleteUser: (userId: string) http.deleteBaseResponsevoid(/user/${userId}), // 上传头像 uploadAvatar: (file: File) http.upload(/user/avatar, file) }; // 用户信息类型 export interface UserInfo { id: string; username: string; email: string; avatar: string; createdAt: string; updatedAt: string; }4.3 Vue组件中使用template div button clickfetchUser获取用户信息/button button clickuploadFile上传文件/button button clickcancelRequest取消请求/button /div /template script setup langts import { ref, onUnmounted } from vue; import { userApi, cancelAllRequests } from /api; const loading ref(false); const userData refany(null); // 获取用户信息 const fetchUser async () { try { loading.value true; const response await userApi.getUserInfo(123); userData.value response.data; } catch (error) { console.error(获取用户信息失败:, error); } finally { loading.value false; } }; // 上传文件 const uploadFile async (event: Event) { const file (event.target as HTMLInputElement).files?.[0]; if (!file) return; try { await userApi.uploadAvatar(file, (progress) { console.log(上传进度: ${progress}%); }); } catch (error) { console.error(上传失败:, error); } }; // 组件卸载时取消所有请求 onUnmounted(() { cancelAllRequests(); }); /script五、高级配置与优化5.1 环境配置管理// src/config/env.ts export interface EnvConfig { baseURL: string; timeout: number; retryCount: number; [key: string]: any; } const envConfigs: Recordstring, EnvConfig { development: { baseURL: http://localhost:3000/api, timeout: 30000, retryCount: 3, enableMock: true }, test: { baseURL: https://test-api.example.com, timeout: 15000, retryCount: 2, enableMock: false }, production: { baseURL: https://api.example.com, timeout: 10000, retryCount: 1, enableMock: false } }; export const getEnvConfig (): EnvConfig { const env process.env.NODE_ENV || development; return envConfigs[env] || envConfigs.development; };5.2 Mock数据支持// src/api/mock/index.ts import MockAdapter from axios-mock-adapter; import request from ../request; // 创建Mock适配器 export const mock new MockAdapter(request.instance, { delayResponse: 500 }); // 用户相关Mock mock.onPost(/user/login).reply(200, { code: 200, data: { token: mock-token-123456 }, message: 登录成功, success: true }); mock.onGet(/\/user\/\d/).reply(config { const userId config.url?.split(/).pop(); return [200, { code: 200, data: { id: userId, username: mockuser, email: mockexample.com, avatar: https://example.com/avatar.jpg }, message: 获取成功, success: true }]; }); // 只在开发环境启用Mock if (process.env.NODE_ENV development) { // 根据配置决定是否启用Mock const { enableMock } getEnvConfig(); if (enableMock) { console.log(Mock数据已启用); } else { mock.restore(); } }5.3 性能监控与日志// 性能监控装饰器 function performanceMonitor(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod descriptor.value; descriptor.value async function(...args: any[]) { const startTime performance.now(); const requestId request_${Date.now()}_${Math.random()}; try { console.log([${requestId}] 开始请求: ${propertyKey}); const result await originalMethod.apply(this, args); const endTime performance.now(); console.log([${requestId}] 请求成功耗时: ${(endTime - startTime).toFixed(2)}ms); return result; } catch (error) { const endTime performance.now(); console.error([${requestId}] 请求失败耗时: ${(endTime - startTime).toFixed(2)}ms, error); throw error; } }; return descriptor; } // 在API类中使用 class UserService { performanceMonitor async getUserInfo(userId: string) { return userApi.getUserInfo(userId); } }六、测试策略6.1 单元测试// tests/unit/request.test.ts import { describe, it, expect, vi, beforeEach } from vitest; import Request from /api/request; import axios from axios; describe(Request Class, () { let request: Request; beforeEach(() { request new Request({ baseURL: http://test.com, timeout: 1000 }); }); it(should create axios instance with correct config, () { expect(request.instance.defaults.baseURL).toBe(http://test.com); expect(request.instance.defaults.timeout).toBe(1000); }); it(should handle GET request successfully, async () { const mockData { id: 1, name: test }; vi.spyOn(request.instance, request).mockResolvedValue({ data: mockData }); const result await request.get({ url: /test }); expect(result).toEqual(mockData); }); it(should handle request errors, async () { vi.spyOn(request.instance, request).mockRejectedValue({ response: { status: 500, data: { message: Server Error } } }); await expect(request.get({ url: /test })).rejects.toThrow(); }); it(should cancel requests, () { const requestId test-request; const cancelToken new axios.CancelToken(cancel { request.cancelRequestSource[requestId] cancel; }); request.cancelRequest(requestId); expect(request.cancelRequestSource[requestId]).toBeUndefined(); }); });6.2 集成测试// tests/integration/userApi.test.ts import { describe, it, expect, beforeAll, afterAll } from vitest; import { setupServer } from msw/node; import { rest } from msw; import { userApi } from /api/modules/user; const server setupServer( rest.post(http://test.com/user/login, (req, res, ctx) { return res( ctx.status(200), ctx.json({ code: 200, data: { token: mock-token }, message: 登录成功, success: true }) ); }) ); describe(User API Integration, () { beforeAll(() server.listen()); afterAll(() server.close()); afterEach(() server.resetHandlers()); it(should login successfully, async () { const response await userApi.login({ username: testuser, password: password123 }); expect(response.code).toBe(200); expect(response.data.token).toBe(mock-token); expect(response.success).toBe(true); }); });七、最佳实践与注意事项7.1 安全考虑Token管理使用HttpOnly Cookie存储敏感tokenCSRF防护添加CSRF Token到请求头请求限流防止API被恶意调用参数验证对请求参数进行严格验证7.2 性能优化请求合并合并短时间内相同请求缓存策略合理使用缓存减少请求懒加载按需加载API模块压缩传输启用Gzip压缩7.3 错误处理策略分级处理区分网络错误、业务错误、系统错误友好提示用户友好的错误消息错误上报错误日志收集和分析自动恢复网络恢复后自动重连八、总结本文详细介绍了Vue项目中Axios的全面封装方案从基础封装到高级功能实现涵盖了基础请求类的创建与配置拦截器的灵活使用类型安全的TypeScript支持请求取消、重试等高级功能模块化API管理Mock数据支持性能监控与测试策略通过这样的封装我们可以实现统一的请求管理和错误处理更好的代码组织和复用增强的开发体验和调试能力更高的应用稳定性和性能封装的程度需要根据项目实际情况进行调整避免过度设计。建议在项目初期就建立良好的请求封装规范随着项目发展逐步完善和优化。大家还有什么更好的实践可以在评论区交流。