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

告别重复代码!用Vue3+TS给Uniapp项目封装一个像axios一样好用的uni.request

用Vue3+TS打造Uniapp专属请求库:从零实现Axios级体验

在Uniapp开发中,uni.request作为基础HTTP客户端,其原生API设计过于底层,导致开发者需要反复处理URL拼接、状态码判断、错误处理等重复逻辑。本文将带你从工程化角度,基于Vue3和TypeScript构建一个类型安全、可维护性高的请求库,让Uniapp项目获得不输axios的开发体验。

1. 为什么需要封装uni.request?

原生uni.request存在几个典型痛点:

  • 冗余代码泛滥:每个请求都需要重复编写header配置、token注入逻辑
  • 类型黑洞:响应数据默认是any类型,失去TS的类型检查优势
  • 错误处理分散:网络异常、登录失效等处理逻辑分散在各处
  • 监控困难:缺乏统一入口实现请求耗时统计、失败重试等高级特性

通过对比原生调用与封装后的效果,差异立现:

// 封装前 uni.request({ url: '/api/user', method: 'GET', header: { Authorization: uni.getStorageSync('token'), 'X-Client': 'uniapp' }, success: (res) => { if (res.statusCode === 401) { uni.navigateTo({ url: '/login' }) } else if (res.statusCode >= 500) { uni.showToast({ title: '服务器异常' }) } else { const data = res.data as { code: number; data: User } // 业务逻辑... } } }) // 封装后 api.get<User>('/user').then(user => { // 直接获得类型化结果 console.log(user.id) })

2. 核心架构设计

2.1 分层设计

完整的请求库应包含以下层次:

  1. 网络层:直接对接uni.request的原始调用
  2. 拦截器层:处理请求/响应转换
  3. 业务适配层:对接项目特定业务逻辑
  4. 类型系统:提供完整的TS类型支持
graph TD A[业务代码] -->|类型化参数| B(业务适配层) B -->|统一错误处理| C(拦截器层) C -->|底层调用| D(网络层) D -->|平台API| E(uni.request)

2.2 类型系统设计

通过泛型实现从请求到响应的全链路类型安全:

interface ApiResponse<T = any> { code: number message: string data: T } type RequestConfig = UniApp.RequestOptions & { /** * 是否跳过全局错误处理 * @default false */ silent?: boolean } class HttpClient { async request<T>(config: RequestConfig): Promise<T> { // 实现细节... } }

3. 实现关键功能

3.1 拦截器系统

Uniapp的拦截器API与axios不同,需要通过uni.addInterceptor注册:

// 请求拦截器 uni.addInterceptor('request', { invoke(args) { // 自动补全基础URL if (!args.url.startsWith('http')) { args.url = `${import.meta.env.VITE_API_BASE}${args.url}` } // 注入认证信息 const token = authStore.token if (token) { args.header = { ...args.header, Authorization: `Bearer ${token}` } } // 记录请求开始时间 args._startTime = Date.now() }, success(args) { // 计算请求耗时 const duration = Date.now() - args._startTime metrics.reportApiTiming(args.url, duration) } })

3.2 错误处理中枢

集中管理各类异常情况:

function handleError(error: unknown) { if (error instanceof NetworkError) { uni.showToast({ title: '网络连接异常', icon: 'none' }) } else if (error instanceof AuthError) { authStore.logout() navigateTo('/login') } else if (error instanceof BusinessError) { if (!error.config.silent) { uni.showToast({ title: error.message, icon: 'none' }) } } // 统一上报错误 monitor.reportError(error) return Promise.reject(error) }

3.3 请求重试机制

对特定错误实现自动重试:

async function requestWithRetry<T>( config: RequestConfig, retryCount = 3 ): Promise<T> { try { return await http.request<T>(config) } catch (error) { if (shouldRetry(error) && retryCount > 0) { await delay(1000 * (4 - retryCount)) // 指数退避 return requestWithRetry(config, retryCount - 1) } throw error } } function shouldRetry(error: unknown): boolean { return ( error instanceof NetworkError || (error instanceof HttpError && error.status >= 500) ) }

4. 高级特性实现

4.1 取消请求

利用Uniapp的Task对象实现取消功能:

const pendingRequests = new Map<string, UniApp.RequestTask>() function addPendingRequest(config: RequestConfig) { const key = generateRequestKey(config) if (pendingRequests.has(key)) { pendingRequests.get(key)?.abort() } const task = uni.request({ ...config, complete() { pendingRequests.delete(key) } }) pendingRequests.set(key, task) } // 在组件卸载时取消关联请求 onUnmounted(() => { pendingRequests.forEach(task => task.abort()) })

4.2 文件上传增强

封装更易用的上传接口:

interface UploadOptions { filePath: string name?: string formData?: Record<string, any> onProgress?: (progress: number) => void } async function uploadFile<T>(url: string, options: UploadOptions) { return new Promise<T>((resolve, reject) => { const task = uni.uploadFile({ url: withBaseUrl(url), filePath: options.filePath, name: options.name || 'file', formData: options.formData, header: withAuthHeader({}), success: (res) => { if (res.statusCode === 200) { resolve(JSON.parse(res.data)) } else { reject(new HttpError(res.statusCode, res.data)) } }, fail: reject }) task.onProgressUpdate((e) => { options.onProgress?.(e.progress) }) }) }

5. 工程化实践

5.1 接口定义自动化

通过Swagger或OpenAPI生成类型定义:

// 自动生成的文件 declare namespace Api { interface User { id: number name: string avatar: string } type GetUserParams = { id: number } type CreateUserPayload = { name: string age?: number } } // 对应实现 export const userApi = { get: (params: Api.GetUserParams) => http.get<Api.User>(`/users/${params.id}`), create: (data: Api.CreateUserPayload) => http.post<Api.User>('/users', data) }

5.2 性能优化技巧

实现请求级缓存:

const cache = new Map<string, { expire: number; data: any }>() async function cachedRequest<T>(key: string, fn: () => Promise<T>, ttl = 60000) { const cached = cache.get(key) if (cached && cached.expire > Date.now()) { return cached.data as T } const data = await fn() cache.set(key, { expire: Date.now() + ttl, data }) return data } // 使用示例 const user = await cachedRequest( `user:${userId}`, () => userApi.get({ id: userId }) )

6. 测试策略

6.1 单元测试重点

针对核心功能编写测试用例:

describe('http client', () => { it('should add auth header', async () => { const mockRequest = jest.spyOn(uni, 'request') authStore.token = 'test-token' await http.get('/test') expect(mockRequest).toBeCalledWith( expect.objectContaining({ header: expect.objectContaining({ Authorization: 'Bearer test-token' }) }) ) }) it('should handle 401 error', async () => { mockResponse(401) const navigateTo = jest.spyOn(router, 'navigateTo') await expect(http.get('/protected')).rejects.toThrow(AuthError) expect(navigateTo).toBeCalledWith('/login') }) })

6.2 Mock方案

实现请求mock的几种方式对比:

方案优点缺点适用场景
uni.mock官方支持功能简单简单场景
MockJS功能丰富需要额外构建配置开发环境
MSW拦截真实请求配置复杂测试环境

7. 实际应用案例

在电商项目中的典型使用:

// api/product.ts export const productApi = { // 带分页查询 list: (query: PaginationQuery) => http.get<PagedResult<Product>>('/products', { params: query }), // 商品详情 detail: (id: number) => http.get<Product>(`/products/${id}`), // 创建商品 create: (data: ProductCreateDto) => http.post<Product>('/products', data), // 上传商品图片 uploadImage: (file: File) => uploadFile<{ url: string }>('/upload', { filePath: file.path }) } // 在组件中使用 const { data: products } = useAsyncData( () => productApi.list({ page: 1, size: 10 }) )

8. 性能监控集成

实现请求全链路监控:

// 在拦截器中收集指标 success(args) { const timing = { url: args.url, duration: Date.now() - args._startTime, status: args.statusCode, method: args.method, timestamp: Date.now() } // 发送到监控平台 monitor.collectApiTiming(timing) // 慢请求告警 if (timing.duration > 3000) { monitor.reportSlowRequest(timing) } }

9. 版本迭代策略

遵循语义化版本控制:

  • 补丁版本:修复bug但不影响使用方式
  • 次要版本:新增功能但向下兼容
  • 主版本:包含破坏性变更

升级指南示例:

## 从v1到v2迁移说明 ### 破坏性变更 - 移除`transformResponse`配置项,改用拦截器实现 - 错误类结构调整: - `HttpError`现在包含`status`和`code`两个属性 - 新增`NetworkError`表示网络层错误 ### 新特性 - 新增请求缓存功能 - 支持AbortController取消请求

10. 生态扩展思路

10.1 插件系统设计

允许通过插件扩展核心功能:

interface HttpPlugin { install(client: HttpClient): void } class CachePlugin implements HttpPlugin { constructor(private options: CacheOptions) {} install(client: HttpClient) { client.interceptors.request.use((config) => { if (this.shouldCache(config)) { return getFromCache(config) } return config }) // 注册响应拦截器... } } // 使用插件 http.use(new CachePlugin({ ttl: 60_000, exclude: ['/auth'] }))

10.2 多平台适配

针对不同平台的特性处理:

function getPlatformHeaders() { return { // 微信小程序特有头 'X-WX-Platform': 'mp-weixin', // App端设备信息 ...(process.env.UNI_PLATFORM === 'app' && { 'X-Device-Id': plus.device.uuid }) } }

在大型项目中,良好的请求层封装可以提升30%以上的开发效率。经过完整封装的请求库,配合完善的类型定义,能让团队开发者像使用axios一样愉快地在Uniapp项目中工作。

http://www.zskr.cn/news/1451414.html

相关文章:

  • 开源维护者植入“删除代码”指令抗议AI,引发全网争议!
  • 如何轻松备份和深度分析微信聊天记录?WeChatMsg实用指南帮你完整掌控社交数据
  • 告别电源噪声!手把手教你用MP2307+SGM3209搭建运放专用±5V低噪声电源
  • 2026年最被低估的AI职业:成为企业“AI推手“,让技能落地并收藏!
  • 【Agent智能体17 | 工具使用-MCP协议】
  • 2026年热门的厚铜高多层线路板/盲埋孔高多层线路板口碑好的厂家推荐 - 品牌宣传支持者
  • 一键部署私人 LLM:Ollama + Docker 极简指南
  • 2026年知名的工业供水原水净化/无锡工业供水系统设备公司哪家好 - 行业平台推荐
  • 2026年评价高的无锡工业供水浓水零排/工业供水除盐处理/工业供水原水净化主流厂家对比评测 - 品牌宣传支持者
  • 一套开源代码的能碳治理实践:MyEMS 数据建模引擎的架构设计思路
  • Windows Server 2022下,手把手配置华为OceanStor存储的iSCSI连接(含MPIO多路径避坑指南)
  • 2026年知名的MIM金属注射成型零件/中山MIM粉末冶金用户口碑推荐厂家 - 行业平台推荐
  • Claude Code 100个真实案例 - 用AI搭建农业物联网监测平台(土壤+气象+作物)
  • 3PEAK思瑞浦 TPA6581-DF0R DFN0.8X0.8-4 运算放大器
  • GitHub问题频发:可靠性堪忧,前端代码臃肿,与竞品对比差距明显!
  • 从CHI 2010看人机交互的范式演进与技术多样性
  • ArcGIS Pro 3 里OSGB转SLPK,我踩过的那些坑和最终的高效批处理方案
  • 如何5分钟配置Zotero-GPT:AI智能文献管理插件终极指南
  • SIM868M32蓝牙版嵌入式AT开发包(含MT6261编译环境与全功能Demo)
  • 一个用于模拟国际空间站通信中延迟/中断容忍网络的开源框架
  • 【Linux网络】网络层IP协议(一)
  • 避坑指南:用bayesplot给Stan模型做可视化,这5个细节新手最容易忽略
  • 面对对象的概念
  • 内容创作者AI工具组合(20年内容基建经验浓缩):从单点提效到组织级智能跃迁的3阶段演进路径
  • 2026年热门的贵州宣传栏/贵州精工字/标识标牌/贵州吸塑灯箱优质供应商推荐 - 品牌宣传支持者
  • 2026年质量好的贵州铝型材挂牌/贵州广告牌用户口碑推荐厂家 - 行业平台推荐
  • ARL Docker 一键部署
  • 容器通过操作系统级虚拟化(OS-level virtualization),直接复用宿主机的 Linux 内核,无需像传统虚拟机(VM)那样为每个实例运行独立的 Guest OS
  • 别再凭感觉画线了!用这个在线工具5分钟搞定PCB电源线宽(附电流计算表)
  • 不只是AX211:一份给联想游戏本装Ubuntu的无线网卡驱动兼容性清单(Y7000P/R7000P等机型实测)