likes
comments
collection
share

uniapp+vue3 setup+ts 开发小程序实战(网络请求封装篇)

作者站长头像
站长
· 阅读数 15

前言

对于请求的封装,主要有以下目的:

  1. 添加typescript类型提示
  2. 在拦截器里面进行一些统一配置,如设置header、针对错误码统一提示等
  3. 多入口场景下,在未登录时,在拦截器里完成无痕登录后再请求。(如从分享页面进入)

其中第三点尤其重要,原因在于:

  1. 小程序存在多个场景打开入口,如消息通知,公众号菜单栏,分享进入等,打开的页面也不尽相同,由于小程序特有的登录逻辑,登录需要调用wx.login API,很显然,在多入口场景下,我们不能在简单粗暴的每个打开的页面都去写一遍调用wx.login,再请求其他接口。
  2. 在拦截器中完成无痕登录,便于我们调试接口:我们在调试一些层级较深的页面接口时,可以在微信开发者工具中选择编译指定的页面,这样我们就无需点击多次进入到需要调试接口的页面。但是如果没有在请求拦截中完成预先登录,我们往往也是无法直接请求调试该页面的接口。

综上目的,这里选择使用PreQuest这个强大的请求库。 PreQuest 是一套 JS 运行时的 HTTP 解决方案,它包含了一些针对不同 JS 运行平台的封装的请求库,并为这些请求库提供了一致的中间件、拦截器、全局配置等功能的体验,还针对诸如 Token 的添加,失效处理,无感知更新、接口缓存、错误重试等常见业务场景,提供了解决方案。可以点击官方文档先做了解

安装

该请求库针对不同的平台提供了不同的安装包,这里安装两个依赖包:

npm i @prequest/miniprogram @prequest/lock -S

封装文件

在src->utils目录下新建requst.ts文件

import { PreQuest, create } from '@prequest/miniprogram'
import Lock from '@prequest/lock'
import { MiddlewareCallback } from '@prequest/types'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore() // 这里将token放在pinia user模块中
declare module '@prequest/types' {
  interface PQRequest {
    skipTokenCheck?: boolean
  }
}

// 全局配置
PreQuest.defaults.baseURL = '请求域名'
// 设置header
PreQuest.defaults.header = {}

const prequest = create(uni.request)

// 无痕刷新中间件
const lock = new Lock({
  getValue() {
    return Promise.resolve(userStore.token)
  },
  setValue(token) {
    userStore.token = token
  },
  clearValue() {
    userStore.token = ''
  },
})
const wrapper = Lock.createLockWrapper(lock)

const refreshToken: MiddlewareCallback = async (ctx, next) => {
  if (ctx.request.skipTokenCheck) return next()

  const token = await wrapper(
    () =>
      new Promise((resolve) => {
        uni.login({
          async success(res) {
            if (res.code) {
              // 登录获取token接口
              prequest('/login', {
                method: 'post',
                skipTokenCheck: true,
                data: { code: res.code },
              }).then((res1) => resolve(res1.data.data.token)) // 注意这里根据后台返回的token结构取值
            }
          },
        })
      }),
  )
  if (ctx.request.header) {
    // header中统一设置token
    ctx.request.header['Authorization'] = `Bearer ${token}`
  }
  await next()
}

// 解析响应
const parse: MiddlewareCallback = async (ctx, next) => {
  await next()
  // 这里抛出异常,会被错误重试中间件捕获
  const { statusCode } = ctx.response
  if (![200, 301, 302].includes(statusCode)) {
    // 在这里可以设置toast提示
    throw new Error(`${statusCode}`)
  }
}

// 实例中间件
prequest.use(refreshToken).use(parse)

export default prequest

请求接口统一管理

首先,我们在src目录下新建api文件夹,专门存放管理请求接口。

新建types.ts

用来存放复用的数据结构,例如请求成功返回的数据结构

// 假设接口响应通过格式
export interface ApiResp {
  code: number
  message: string
  data: any
  meta?: {
    pageSize: number
    total: number
    current: number
  }
}

接着按照功能模块进行管理,比如我们有用户相关的接口集合,在api下新建user.ts和user.model.ts两个文件,.model文件用于定义接口interface,这里值得注意的是一个接口对应两个interface,分别定义请求参数及返回的数据结构,这里可以约定统一命名格式为:参数为“Parm”后缀,返回数据为“Resp”后缀,如下示例:

user.model.ts

import { ApiResp } from './types'

export interface GetUserListParm {
  position: number
}

export interface GetUserListResp extends ApiResp {
  data: GetListData[]
}

export interface GetUserListData {
  name: string
  position: number
}

user.ts

import * as UserModel from './user.model'
import prerequest from '@/utils/request'

class UserService {
  // 获取列表
  static getList(params: UserModel.GetListParm) {
    return prerequest.post<UserModel.GetListResp>(
      '/list',
      { params },
    )
  }
}

export default UserService

上面文件定义了一个叫做getList的请求方法,GetUserListParm和GetUserListResp分别定义该请求的参数及返回数据结构

页面中使用

<script setup lang="ts">
  import UserService from '@/api/user'
 
  async function getData() {
    const params = {
      position: 1,
    }
    const res = await UserService.getList(params)
    const { code, data } = res.data
    if (code === 0) {
      console.log(data) // 这里访问data会有类型提示
    }
  }
  getData()
</script>

至此,我们已经完成了基本网络请求的封装和使用。而除了普通的网络数据请求,我们还可能遇到上传下载文件的需求,这里我们也一并做统一封装处理。

封装上传下载公共方法

安装依赖拓展包

npm i @prequest/miniprogram-addon -S

通常来说,后台提供的上传接口都是公共的,我们可以在api目录下新建个common.ts文件,里面存放一些公共请求方法,例如上传下载

在type.ts中新增内容

// 文件上传成功返回数据
export interface UploadResp {
  code: number
  msg: string
  data: {
    filename: string
    fileUrl: string
  }
}

修改utils->request.ts,增加createUpload和createDownload的参数声明:

declare module '@prequest/types' {
  interface PQRequest {
    name?: string
    url?: string
    filePath?: string
    formData?: Common
    skipTokenCheck?: boolean
  }
}

common.ts:

import { createUpload, createDownload } from '@prequest/miniprogram-addon'
import { UploadResp } from './types'

class CommonService {
  // 上传文件
  static uploadFile(filePath: string) {
    const upload = createUpload(uni.uploadFile, {
      name: 'imgFile',
      filePath,
      formData: { fileName: 'testName' },
    })
    return upload<UploadResp>('/fileUpload/imgUpload')
  }
  // 下载文件
  static downloadFile(url: string) {
    const download = createDownload(uni.downloadFile, {
      url,
    })
    return download(url)
  }
}

export default CommonService

页面中使用示例:

import CommonService from '@/api/common'

// 选择照片或视频
function chooseMedia(mediaType: 'image' | 'video' = 'image') {
  uni.chooseMedia({
    count: 1,
    mediaType: [mediaType],
    sizeType: ['compressed'],
    maxDuration: 60,
    success(res) {
      const path = res.tempFiles.map((item) => item.tempFilePath)
      uploadFile(path[0])
    },
    fail() {
      // $toast("选取图片失败");
    },
  })
}

async function uploadFile(path: string) {
  const res = await CommonService.uploadFile(path)
  const { code, data } = res.data
  if (code === 0) {
    // 上传成功
  }
 }

总结

以上内容完成了对数据请求交互及文件上传下载的封装使用,除了这些,该请求库还提供了其他一些请求处理中间件,例如请求缓存,请求错误重试,具体使用大家移步官方文档查阅即可。

值得一提的是,很多时候,我们一加载页面需要并发请求多个接口,很多人习惯直接这么写:

onload() {
  getData1()
  getData2()
  getData3()
}

这时候我们可以借助Promise 的两个api进行优化:

  • Promise.allSettled: 用于并发请求有多个彼此不依赖的异步任务
  • Promise.all: 用于彼此相互依赖或者在其中任何一个reject时立即结束

具体使用方法,可以移步MDN文档了解

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿