UniApp——对uni.request()进行封装,实现拦截器和typescript支持

UniApp——对uni.request()进行封装,实现拦截器和TypeScript支持

一、背景与问题

在UniApp开发中,网络请求是不可避免的核心功能。然而原生的uni.request()存在几个痛点:

  1. 缺乏统一的请求管理:不同页面重复的请求配置需要重复书写
  2. 缺少拦截器机制:无法统一处理请求前的参数加工和响应后的数据处理
  3. TypeScript类型缺失:原生接口缺乏类型定义,导致开发时容易出现类型错误
  4. 错误处理分散:不同请求的错误处理逻辑需要分别实现

为解决这些问题,我们需要对uni.request()进行封装,构建一个包含拦截器、类型定义和统一错误处理的网络请求库。

二、基本原理

UniApp的网络请求机制基于uni.request(),其底层会根据运行环境自动适配到不同平台的API。通过封装,我们可以实现:

  1. 拦截器模式:通过beforeRequest和afterResponse钩子处理请求/响应
  2. 类型系统:使用TypeScript定义请求和响应的类型结构
  3. 统一错误处理:集中处理网络错误、业务错误和异常情况
  4. 跨平台兼容:确保在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(),我们构建了一个具有拦截器、类型支持和统一错误处理的网络请求库。这种封装方式在实际开发中具有显著优势:

  1. 提高代码复用性:统一的请求接口减少重复代码
  2. 增强可维护性:通过拦截器集中处理业务逻辑
  3. 提升开发效率:TypeScript类型支持减少运行时错误
  4. 保障稳定性:统一的错误处理机制提高系统鲁棒性

需要注意的是,这种封装方案并非万能,需要根据具体业务需求进行取舍。对于简单的场景可以直接使用原生接口,而对于复杂的业务系统,这种封装方式能显著提升开发效率和代码质量。在实际项目中,建议结合具体需求选择合适的封装方案,并持续优化拦截器逻辑和错误处理机制。

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日