uni-app请求封装实践:提升开发效率与维护性

发布时间:2026/8/5 5:26:31
uni-app请求封装实践:提升开发效率与维护性 1. 为什么需要封装uni-app的request请求在uni-app开发中直接使用uni.request()进行网络请求看似简单但随着项目规模扩大这种原始方式会暴露出诸多问题。我接手过不少从简单Demo发展而来的项目都因为早期没有做好请求封装而陷入维护困境。1.1 原始请求方式的痛点直接调用uni.request()最明显的问题是代码重复。每个页面都需要重复编写请求的URL、method、header等配置。当后端接口变更时开发者需要逐个修改这些散落在各处的请求代码极易遗漏。另一个痛点是缺乏统一的错误处理。网络异常、权限验证失败、业务逻辑错误等都需要在每次请求时单独处理导致大量重复的error回调代码。我曾见过一个项目中有37处几乎相同的错误提示代码维护起来简直是噩梦。1.2 统一封装的必要性通过封装request可以实现统一配置baseURL避免硬编码集中管理请求/响应拦截器标准化错误处理流程简化业务代码中的调用方式方便后期添加全局功能如自动重试、请求取消等在我的一个电商项目中封装request后接口相关代码量减少了60%新成员上手速度提升明显后端接口迁移时只需修改一处配置。2. 基础封装方案设计2.1 创建request工具类首先在项目utils目录下创建request.jsconst BASE_URL https://api.yourdomain.com const request (options) { return new Promise((resolve, reject) { uni.request({ url: BASE_URL options.url, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, ...options.header }, success: (res) { // 统一处理响应 }, fail: (err) { // 统一处理错误 } }) }) } export default request2.2 添加拦截器机制拦截器是封装的核心价值所在。我们可以实现// 请求拦截器队列 const requestInterceptors [] // 响应拦截器队列 const responseInterceptors [] const request (options) { // 执行请求拦截器 requestInterceptors.forEach(interceptor { options interceptor(options) || options }) return new Promise((resolve, reject) { uni.request({ ...options, success: (res) { // 执行响应拦截器 responseInterceptors.forEach(interceptor { res interceptor(res) || res }) resolve(res) }, fail: reject }) }) } // 添加拦截器方法 request.addRequestInterceptor (interceptor) { requestInterceptors.push(interceptor) } request.addResponseInterceptor (interceptor) { responseInterceptors.push(interceptor) }3. 高级功能实现3.1 自动重试机制网络不稳定时自动重试可以提升用户体验const requestWithRetry async (options, retryCount 3) { try { return await request(options) } catch (err) { if (retryCount 0) throw err await new Promise(resolve setTimeout(resolve, 1000)) return requestWithRetry(options, retryCount - 1) } }3.2 请求取消功能在页面卸载时取消未完成的请求const pendingRequests new Map() const request (options) { const controller new AbortController() const requestId Symbol(requestId) pendingRequests.set(requestId, controller) return new Promise((resolve, reject) { uni.request({ ...options, signal: controller.signal, complete: () { pendingRequests.delete(requestId) }, success: resolve, fail: reject }) }) } // 在页面onUnload时调用 function cancelPendingRequests() { pendingRequests.forEach(controller controller.abort()) pendingRequests.clear() }4. 最佳实践与避坑指南4.1 错误处理标准化建议定义业务错误码规范// 响应拦截器示例 responseInterceptors.push((response) { const { data, statusCode } response if (statusCode 401) { // 跳转到登录页 uni.navigateTo({ url: /pages/login/login }) throw new Error(未授权) } if (data.code ! 0) { uni.showToast({ title: data.message || 业务错误, icon: none }) throw new Error(data.message) } return data.data })4.2 性能优化技巧合理设置超时时间// 根据不同接口类型设置不同超时 const TIMEOUT { default: 10000, upload: 30000, download: 60000 } uni.request({ ...options, timeout: TIMEOUT[options.timeoutType || default] })请求合并对于高频但数据量小的请求可以使用节流批量请求策略。缓存策略对静态数据实现内存缓存const cache new Map() const cachedRequest async (options) { const cacheKey JSON.stringify(options) if (cache.has(cacheKey)) { return cache.get(cacheKey) } const result await request(options) cache.set(cacheKey, result) return result }4.3 多环境配置通过环境变量区分不同环境的API地址// config.js const ENV process.env.NODE_ENV || development const API_CONFIG { development: { baseURL: https://dev.api.com }, production: { baseURL: https://api.com } } export default API_CONFIG[ENV]5. TypeScript增强版实现对于TS项目可以增加类型支持interface RequestOptions { url: string method?: GET | POST | PUT | DELETE data?: any header?: Recordstring, string timeout?: number } interface ResponseDataT any { code: number message: string data: T } const request T(options: RequestOptions): PromiseT { return new Promise((resolve, reject) { uni.request({ ...options, success: (res: { data: ResponseDataT }) { if (res.data.code 0) { resolve(res.data.data) } else { reject(new Error(res.data.message)) } }, fail: reject }) }) }6. 微信小程序特殊处理微信小程序有一些特殊限制需要注意域名白名单必须在小程序后台配置合法域名并发限制wx.request最大并发限制为10个HTTPS强制必须使用HTTPS协议数据大小限制单次请求数据不得超过1MB针对这些限制可以添加专门的处理逻辑// 检查是否是微信小程序环境 const isWeapp process.env.UNI_PLATFORM mp-weixin // 微信小程序专用逻辑 if (isWeapp) { request.addRequestInterceptor((options) { // 检查数据大小 if (JSON.stringify(options.data).length 1024 * 1024) { throw new Error(请求数据超过1MB限制) } return options }) }7. 完整实现示例以下是整合所有功能的完整实现import config from ./config class Request { constructor() { this.baseURL config.baseURL this.requestInterceptors [] this.responseInterceptors [] this.pendingRequests new Map() } request(options) { // 合并配置 const mergedOptions { url: this.baseURL options.url, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, ...options.header }, timeout: options.timeout || 10000 } // 执行请求拦截器 this.requestInterceptors.forEach(interceptor { Object.assign(mergedOptions, interceptor(mergedOptions) || {}) }) const controller new AbortController() const requestId Symbol(requestId) this.pendingRequests.set(requestId, controller) return new Promise((resolve, reject) { uni.request({ ...mergedOptions, signal: controller.signal, complete: () { this.pendingRequests.delete(requestId) }, success: (res) { // 执行响应拦截器 let processedRes res this.responseInterceptors.forEach(interceptor { processedRes interceptor(processedRes) || processedRes }) resolve(processedRes) }, fail: (err) { reject(this.normalizeError(err)) } }) }) } normalizeError(err) { // 统一错误格式 return { message: err.errMsg || 网络错误, code: err.statusCode || -1 } } get(url, params {}, options {}) { return this.request({ url, method: GET, data: params, ...options }) } post(url, data {}, options {}) { return this.request({ url, method: POST, data, ...options }) } cancelAll() { this.pendingRequests.forEach(controller controller.abort()) this.pendingRequests.clear() } addRequestInterceptor(interceptor) { this.requestInterceptors.push(interceptor) } addResponseInterceptor(interceptor) { this.responseInterceptors.push(interceptor) } } export default new Request()使用时import request from /utils/request // 添加拦截器 request.addRequestInterceptor(options { const token uni.getStorageSync(token) if (token) { options.header { ...options.header, Authorization: Bearer ${token} } } return options }) request.addResponseInterceptor(response { if (response.statusCode 401) { uni.navigateTo({ url: /login }) throw new Error(请重新登录) } return response.data }) // 发起请求 async function fetchData() { try { const data await request.get(/api/data) console.log(data) } catch (err) { uni.showToast({ title: err.message, icon: none }) } }8. 常见问题解决方案8.1 跨域问题处理虽然uni-app理论上可以跨平台解决跨域问题但在实际开发中仍可能遇到开发环境代理// vue.config.js module.exports { devServer: { proxy: { /api: { target: http://your-api-server, changeOrigin: true, pathRewrite: { ^/api: } } } } }生产环境Nginx配置location /api/ { proxy_pass http://api-server/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }8.2 文件上传特殊处理文件上传需要修改Content-Typeconst uploadFile (filePath, formData {}) { return new Promise((resolve, reject) { uni.uploadFile({ url: config.baseURL /upload, filePath, name: file, formData, header: { Content-Type: multipart/form-data }, success: resolve, fail: reject }) }) }8.3 登录状态维护推荐使用拦截器自动处理tokenrequest.addRequestInterceptor(options { const token uni.getStorageSync(token) if (token) { options.header { ...options.header, Authorization: Bearer ${token} } } return options }) request.addResponseInterceptor(async response { if (response.statusCode 401) { // 尝试刷新token try { const newToken await refreshToken() uni.setStorageSync(token, newToken) // 重试原始请求 return request(response.config) } catch (err) { // 跳转登录 uni.navigateTo({ url: /login }) throw err } } return response.data })9. 单元测试策略为保证封装代码质量应编写单元测试describe(Request, () { let request beforeEach(() { request new Request() request.baseURL https://test.api }) it(should add baseURL to request, async () { const mockRequest jest.spyOn(uni, request) mockRequest.mockImplementation(({ success }) { success({ statusCode: 200, data: {} }) }) await request.get(/test) expect(mockRequest).toBeCalledWith( expect.objectContaining({ url: https://test.api/test }) ) }) it(should handle network error, async () { jest.spyOn(uni, request).mockImplementation(({ fail }) { fail({ errMsg: request:fail }) }) await expect(request.get(/test)).rejects.toEqual({ message: 网络错误, code: -1 }) }) })10. 性能监控与优化最后我们可以为请求添加性能监控request.addRequestInterceptor(options { options.metadata { startTime: Date.now() } return options }) request.addResponseInterceptor(response { const duration Date.now() - response.config.metadata.startTime console.log(请求 ${response.config.url} 耗时 ${duration}ms) if (duration 5000) { reportSlowRequest(response.config.url, duration) } return response })对于大型项目建议将监控数据上报到APM系统帮助发现性能瓶颈。