UniApp请求封装踩坑实录:从拦截器失效到TS类型飘红,我是如何解决的?

发布时间:2026/6/3 15:21:39

UniApp请求封装踩坑实录:从拦截器失效到TS类型飘红,我是如何解决的? UniApp请求封装实战从拦截器陷阱到TS类型安全的全链路解决方案第一次在UniAppVue3TS项目中封装网络请求层时我天真地以为这不过是把axios那套逻辑移植过来。直到拦截器莫名失效、TypeScript类型检查全线飘红、异步请求状态管理失控——这些坑让我在深夜调试时差点把键盘摔了。如果你也正在经历这种痛苦不妨看看这份用头发换来的实战指南。1. 拦截器失效的真相UniApp的异步陷阱很多人习惯在main.ts里直接初始化拦截器但在UniApp中这可能导致拦截器隐形。我遇到过最诡异的情况是拦截器代码明明执行了但请求就是不走拦截逻辑。根本原因在于UniApp的运行时加载机制。1.1 正确的拦截器注册时机// 错误示范直接放在main.ts顶层 uni.addInterceptor(request, {...}) // 正确姿势确保在应用ready后注册 onLaunch(() { initHttpInterceptor() // 封装好的拦截器初始化函数 })关键发现UniApp的API模块是动态加载的过早注册会导致拦截器绑定失败。实测发现在onLaunch或页面onLoad阶段注册最可靠。1.2 拦截器参数传递的黑盒现象当你的拦截器接收到的options总是缺少某些字段时这不是幻觉。UniApp的请求参数处理有个隐藏特性uni.addInterceptor(request, { invoke(args) { // args会被深拷贝直接修改可能不生效 const newArgs { ...args, url: processUrl(args.url), // 必须返回新对象 header: mergeHeaders(args.header) } return newArgs // 关键必须返回完整配置 } })对比传统axios拦截器UniApp的这个设计差异常容易踩坑特性Axios拦截器UniApp拦截器参数修改方式直接修改config必须返回新对象异步支持完全支持有限支持错误捕获范围全局可能遗漏部分错误2. TS类型体操从Any到类型安全初期我的请求封装返回的都是Promiseany直到项目大了才发现类型检查形同虚设。经过三次重构最终定型这套类型方案2.1 三层泛型设计// 基础响应结构 type BaseResponseT any { code: number message: string data: T timestamp: number } // 请求配置扩展 type RequestConfigT any UniApp.RequestOptions { mock?: boolean transformResponse?: (res: T) any } // 核心请求方法 function requestT any, R BaseResponseT( config: RequestConfigR ): PromiseR { // 实现逻辑... }使用示例// 获取用户信息 interface UserProfile { name: string age: number avatar: string } const fetchUser () requestUserProfile({ url: /api/user }) // 此时response.data自动推断为UserProfile类型 fetchUser().then(res { console.log(res.data.age) // 完美类型提示 })2.2 类型守卫的妙用针对后端可能返回的各种异常数据结构我们可以在类型层面做安全防护function isSuccessResponse(res: any): res is BaseResponse { return ( typeof res object code in res data in res res.code 200 ) } // 在拦截器中 if (isSuccessResponse(response)) { // 此处response自动获得正确类型 return response.data } else { throw new Error(Invalid response structure) }3. 异常处理的艺术不只是Toast那么简单见过太多项目用uni.showToast处理所有异常直到我们的测试人员发现连续快速触发多个请求失败时Toast会形成弹窗风暴。这是我们的改进方案3.1 异常分级处理机制const ERROR_STRATEGY { NETWORK_ERROR: { level: blocking, handler: () navigateTo(/network-error) }, AUTH_EXPIRED: { level: critical, handler: () { clearToken() showModal({ title: 登录过期, content: 请重新登录 }) } }, DEFAULT: { level: notice, handler: (msg) showToast(msg) } } // 使用示例 function handleError(error) { const strategy ERROR_STRATEGY[error.code] || ERROR_STRATEGY.DEFAULT // 防抖处理 if (!this.debouncing) { strategy.handler(error.message) this.debouncing true setTimeout(() this.debouncing false, 2000) } }3.2 请求重试的智能策略对于网络波动导致的失败我们实现了指数退避重试async function requestWithRetry( config: RequestConfig, retries 3, delay 1000 ): Promiseany { try { return await request(config) } catch (error) { if (shouldRetry(error) retries 0) { await new Promise(resolve setTimeout(resolve, delay) ) return requestWithRetry( config, retries - 1, delay * 2 // 每次延迟时间翻倍 ) } throw error } } function shouldRetry(error) { return [ NETWORK_ERROR, TIMEOUT, SERVER_UNAVAILABLE ].includes(error.code) }4. 性能优化看不见的细节处理当我们的用户列表页达到100并发请求时发现了严重的性能瓶颈。以下是关键的优化点4.1 请求去重与缓存const pendingRequests new Map() function getRequestKey(config) { return ${config.method}-${config.url}-${JSON.stringify(config.data)} } async function deduplicatedRequest(config) { const key getRequestKey(config) if (pendingRequests.has(key)) { return pendingRequests.get(key) } const promise request(config).finally(() { pendingRequests.delete(key) }) pendingRequests.set(key, promise) return promise }4.2 智能超时设置根据网络环境动态调整超时时间function getDynamicTimeout() { const { networkType } uni.getNetworkTypeSync() const timeouts { wifi: 10000, 4g: 15000, 3g: 20000, 2g: 30000, unknown: 25000 } return timeouts[networkType] || 15000 } // 在请求配置中 uni.request({ timeout: getDynamicTimeout() })5. 实战中的那些骚操作项目上线后我们收集了一些特殊场景的解决方案这几个技巧可能会救你一命5.1 文件上传的进度劫持UniApp的upload API进度事件有时不触发这是我们找到的替代方案uni.uploadFile({ // ...其他配置 formData: { _progress_monitor: 1 // 后端配合返回进度数据 }, success(res) { if (res.data) { const { progress } JSON.parse(res.data) // 使用假进度数据 } } })5.2 请求取消的另类实现由于UniApp没有真正的AbortController我们通过标记法模拟let cancelToken { cancelled: false } function cancelableRequest(config) { if (config.cancelToken) { config.cancelToken.cancel () { config.cancelToken.cancelled true } } return new Promise((resolve, reject) { uni.request({ ...config, success: (res) { if (!config.cancelToken?.cancelled) { resolve(res) } }, fail: reject }) }) }在最近一次项目迭代中这套请求封装经受住了单日百万级请求的考验。最让我欣慰的不是零崩溃的记录而是新同事能在完全不看文档的情况下凭借类型提示就能正确调用所有API。

相关新闻