UniApp——对uni.request()进行封装,实现拦截器和typescript支持
UniApp——对uni.request()进行封装,实现拦截器和TypeScript支持
一、背景与问题
在UniApp开发中,网络请求是不可避免的核心功能。然而原生的uni.request()存在几个痛点:
- 缺乏统一的请求管理:不同页面重复的请求配置需要重复书写
- 缺少拦截器机制:无法统一处理请求前的参数加工和响应后的数据处理
- TypeScript类型缺失:原生接口缺乏类型定义,导致开发时容易出现类型错误
- 错误处理分散:不同请求的错误处理逻辑需要分别实现
为解决这些问题,我们需要对uni.request()进行封装,构建一个包含拦截器、类型定义和统一错误处理的网络请求库。
二、基本原理
UniApp的网络请求机制基于uni.request(),其底层会根据运行环境自动适配到不同平台的API。通过封装,我们可以实现:
- 拦截器模式:通过
beforeRequest和afterResponse钩子处理请求/响应 - 类型系统:使用TypeScript定义请求和响应的类型结构
- 统一错误处理:集中处理网络错误、业务错误和异常情况
- 跨平台兼容:确保在H5、小程序、App等平台都能正常运行
三、环境准备
# 创建UniApp项目
vue create uni-app-project
cd uni-app-project
# 安装TypeScript依赖
npm install --save-dev typescript @types/uni四、核心实现
1. 定义类型接口
// src/types/request.ts
export interface RequestConfig {
url: string;
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
data?: Record<string, any>;
headers?: Record<string, string>;
timeout?: number;
}
export interface ResponseData<T = any> {
code: number;
message: string;
data: T;
}2. 实现拦截器逻辑
// src/utils/request.ts
import { uni } from '@dcloudio/uni-app'
type RequestInterceptor = (config: RequestConfig) => RequestConfig | void
type ResponseInterceptor = (response: ResponseData<any>) => ResponseData<any> | void
export class RequestService {
private requestInterceptors: RequestInterceptor[] = []
private responseInterceptors: ResponseInterceptor[] = []
// 添加请求拦截器
useRequestInterceptor(interceptor: RequestInterceptor): void {
this.requestInterceptors.push(interceptor)
}
// 添加响应拦截器
useResponseInterceptor(interceptor: ResponseInterceptor): void {
this.responseInterceptors.push(interceptor)
}
// 发起请求
async request<T = any>(config: RequestConfig): Promise<ResponseData<T>> {
// 请求拦截
let modifiedConfig = { ...config }
for (const interceptor of this.requestInterceptors) {
modifiedConfig = interceptor(modifiedConfig) || modifiedConfig
}
try {
const res = await uni.request({
url: modifiedConfig.url,
method: modifiedConfig.method || 'GET',
data: modifiedConfig.data,
header: modifiedConfig.headers,
timeout: modifiedConfig.timeout || 10000
})
// 响应拦截
let modifiedRes = { ...res }
for (const interceptor of this.responseInterceptors) {
modifiedRes = interceptor(modifiedRes) || modifiedRes
}
return modifiedRes
} catch (err: any) {
console.error('网络请求失败:', err)
throw new Error(`请求失败: ${err.message}`)
}
}
}3. 类型增强与错误处理
// src/utils/request.ts
// 增加类型校验
export function isRequestConfig(config: any): config is RequestConfig {
return typeof config === 'object' && 'url' in config
}
// 增加错误处理
export function handleRequestError(error: any, message: string): void {
if (typeof uni === 'undefined') return
uni.showToast({
title: message,
icon: 'none',
duration: 2000
})
console.error('请求错误:', error)
}五、完整案例
1. 登录流程示例
// pages/login/login.vue
<script lang="ts">
import { RequestService } from '@/utils/request'
export default {
data() {
return {
username: '',
password: ''
}
},
methods: {
async login() {
const service = new RequestService()
// 添加请求拦截器
service.useRequestInterceptor((config) => {
// 添加token到请求头
if (typeof uni === 'object' && 'getStorageSync' in uni) {
const token = uni.getStorageSync('token')
if (token) {
config.headers = { ...config.headers, Authorization: `Bearer ${token}` }
}
}
return config
})
// 添加响应拦截器
service.useResponseInterceptor((res) => {
if (res.code === 200) {
// 存储token
if (typeof uni === 'object' && 'setStorageSync' in uni) {
uni.setStorageSync('token', res.data.token)
}
return res
} else {
// 处理业务错误
handleRequestError(null, res.message)
throw new Error(res.message)
}
})
try {
const res = await service.request({
url: 'https://api.example.com/login',
method: 'POST',
data: {
username: this.username,
password: this.password
}
})
console.log('登录成功:', res)
} catch (err) {
console.error('登录失败:', err)
}
}
}
}
</script>六、源码解析
1. 拦截器执行流程
// 拦截器执行逻辑
for (const interceptor of this.requestInterceptors) {
modifiedConfig = interceptor(modifiedConfig) || modifiedConfig
}- 每个拦截器函数接收当前配置对象,可以修改或返回新的配置
- 如果返回
undefined,则使用原始配置 - 所有拦截器按添加顺序依次执行
2. 异常处理机制
try {
const res = await uni.request(...)
} catch (err: any) {
console.error('网络请求失败:', err)
throw new Error(`请求失败: ${err.message}`)
}- 使用
try-catch捕获网络错误 - 通过
uni.showToast统一展示错误提示 - 将错误信息抛出供调用方处理
七、进阶使用
1. 请求重试机制
// 增加重试逻辑
useRequestInterceptor((config) => {
if (config.retryCount === undefined) {
config.retryCount = 0
}
if (config.retryCount < 3) {
config.retryCount++
return config
}
return undefined
})2. 加载状态管理
// 在组件中管理loading状态
onBeforeMount() {
this.loading = true
}
onUnmounted() {
this.loading = false
}3. 缓存策略实现
// 增加缓存逻辑
useResponseInterceptor((res) => {
if (res.code === 200 && res.data && res.data.cacheable) {
uni.setStorageSync('cache_' + res.data.id, res.data)
}
return res
})八、性能与工程实践
1. 性能优化策略
| 优化点 | 解决方案 |
|---|---|
| 拦截器性能 | 避免在拦截器中执行耗时操作,使用缓存 |
| 跨域问题 | 配置服务器CORS策略,使用代理服务器 |
| 冗余请求 | 使用防抖/节流控制频繁请求 |
| 响应体过大 | 增加响应压缩和分页支持 |
2. 安全风险控制
- Token泄露:使用HTTPS传输,避免在URL中暴露敏感信息
- CSRF攻击:增加请求头验证机制
- 数据校验:在拦截器中进行数据格式校验
- 权限控制:在服务器端进行严格的权限验证
3. 错误处理方案
| 错误类型 | 处理方式 |
|---|---|
| 网络错误 | 显示网络异常提示 |
| 业务错误 | 根据错误码进行相应处理 |
| 服务器错误 | 重试机制或提示服务器异常 |
| 未知错误 | 显示通用错误提示 |
九、常见问题与踩坑
1. 常见错误及解决方法
| 错误现象 | 原因 | 解决方案 |
|---|---|---|
| 拦截器未生效 | 未正确使用useRequestInterceptor方法 | 确保在调用request()前注册拦截器 |
| 类型错误 | 缺少类型定义 | 完善RequestConfig和ResponseData类型 |
| 请求未触发 | 未正确调用request()方法 | 检查调用位置和参数 |
| token失效 | 未及时更新token | 在响应拦截器中更新token存储 |
2. 典型踩坑案例
// 错误示例:未处理异步操作
useRequestInterceptor((config) => {
// 错误:未处理异步操作
setTimeout(() => {
config.headers = { ...config.headers, Authorization: 'Bearer token' }
}, 1000)
return config
})问题:拦截器中使用setTimeout会导致配置未生效
改进:使用Promise处理异步逻辑
useRequestInterceptor((config) => {
return new Promise((resolve) => {
setTimeout(() => {
config.headers = { ...config.headers, Authorization: 'Bearer token' }
resolve(config)
}, 1000)
})
})十、最佳实践
1. 推荐方案
- 使用
uni.request()封装统一的网络请求库 - 采用拦截器模式统一处理请求/响应
- 通过TypeScript定义严格的类型结构
- 分离请求拦截器和响应拦截器
- 实现统一的错误处理机制
- 增加请求重试和缓存策略
2. 使用建议
应该使用:
- 需要统一处理token、签名等请求参数时
- 需要统一处理响应格式和错误码时
- 需要实现加载状态管理时
- 需要实现请求重试机制时
不应该使用:
- 简单的页面级请求(可直接使用
uni.request()) - 需要高度定制化请求的场景(可考虑使用axios等第三方库)
- 对性能要求极高的场景(避免过多拦截器处理)
十一、总结
通过封装uni.request(),我们构建了一个具有拦截器、类型支持和统一错误处理的网络请求库。这种封装方式在实际开发中具有显著优势:
- 提高代码复用性:统一的请求接口减少重复代码
- 增强可维护性:通过拦截器集中处理业务逻辑
- 提升开发效率:TypeScript类型支持减少运行时错误
- 保障稳定性:统一的错误处理机制提高系统鲁棒性
需要注意的是,这种封装方案并非万能,需要根据具体业务需求进行取舍。对于简单的场景可以直接使用原生接口,而对于复杂的业务系统,这种封装方式能显著提升开发效率和代码质量。在实际项目中,建议结合具体需求选择合适的封装方案,并持续优化拦截器逻辑和错误处理机制。
评论已关闭