在这里插入图片描述

大家好,我是Aliom252181,一个佛系且资质平平的前端coder,今天分享下我是如何使用Typescript封装wx.request的。

写在前面

本篇文章适合有封装TS版本小程序请求需求的coder,通过本篇阅读,你将会了解到:

  • TS代码提示;
  • 单例模式;
  • 每个接口都可以灵活配置请求头、超时时间等;
  • 取消原生嵌套地狱写法,更符合阅读逻辑。

微信小程序自带的wx.request请求方式使用方式如下:

wx.request({
      url: 'http://host/api/key', //请求的接口地址
      method:"get",               //http请求数据的方式
      data:{name:'',age:''},      //请求的参数,如name和id
      //请求头设置,根据需要自己设置
      header:{
        'content-type':"application/json"
      },
      //请求成功时的回调函数
      success(res){
        console.log(res);
      },
      //请求失败的回调函数
      fail(msg){
          console.log(msg);
      }
    })

如果不封装的话,每次使用请求都会造成大段重复代码占用代码区,对阅读极其不利,也降低了开发效率,为了解决这个问题,一起来开启封装之旅吧。

基础类型封装

首先我们要先定义一些我们需要的类型,例如请求参数类型,返回数据类型,还有一些枚举类型,现在开始封装:

封装HTTP请求配置

// HTTP请求方法枚举
export enum HttpMethod {
  GET,
  POST
}

// HTTP请求配置
interface RequestConfig {
  /** API路径 */
  url?: string
  /** Method类型 */
  method?: HttpMethod
  /** 接口返回数据 */
  data?: any
  /** 无TOKEN触发异常捕获时,是否执行异常逻辑 */
  needToken?: boolean
  /** Header头部 */
  header?: object
}

其中needToken是我们的业务逻辑,此参数主要作用于一些需要权限接口,如果我们不想让他在request层级触发我们的逻辑,例如不想触发登录验证,就可以在接下来处理返回code时使用。

封装请求返回数据类型

// 继承中间类型,data声明为any
interface AnyResult extends WechatMiniprogram.RequestSuccessCallbackResult {
  data: any
}
// 从中间类型继承一个泛型接口,data声明为泛型
interface SpecResult<T> extends AnyResult {
  data: T
}
// 声明业务数据类型
export interface MyAwesomeData {
  code: number
  msg: string
  data: any
}

这一步是定义返回参的类型,我们希望最终的返回结果为{code,msg,data},便于我们的使用。其中注意WechatMiniprogram是微信提供的类型,需要大家在TS小程序项目内使用,也可以使用NPM包miniprogram-api-typings来作为校验,如果是后者,要注意在tsconfig.jsontypeRoots字段内设置此包地址,例如:

"typeRoots": [
    "./node_modules/miniprogram-api-typings"
 ],

环境配置

除了正式环境,大家的日常开发中免不了要进行多环境切换,这里写成配置,方便开发使用。

首先新建env.ts文件作为环境配置中心,这里我已常用的三环境为例,大家自行扩展。

// env.ts
let envs = {
  dev: {
    host: 'http://192.168.0.1:20087/',
    imgHost: 'http://192.168.0.2:20087/'
  },
  test:{
    host: 'http://192.168.0.1:20086/',
    imgHost: 'http://192.168.0.2:20086/'
  },
  prod: {
    host: 'https://XXXXX.com/',
    imgHost: 'http://image.XXXXX.com/'
  },
}

module.exports = envs

接下来新建config.ts文件作为小程序配置中心,用来控制环境切换和其他配置。

// config.ts

// 基础共同的配置
let baseConfig = {
  // 小程序appid
  appid: '……',
  // 环境
  ENV:'dev'
}

//环境文件
let envConfig = require('./env')
// 合并配置
let config = Object.assign(baseConfig, envConfig[baseConfig.ENV])

// 导出配置
module.exports = config

核心代码封装

恭喜你,看到这里,你已经完成了所有封装的前期准备工作,那么我们开始正式封装。

定义核心请求类HttpRequest

这里我们使用单例模式定义此核心类,好处有三:

  • 单例模式可以确保所有对象都访问唯一实例;
  • 因为类控制了实例化过程,所以类可以灵活更改实例化过程;
  • 因为只有一个实例,所以减少内存开支和系统的性能开销。

虽然我很赞同那句:最好的注释就是没有注释",但是这在工具类里显然不生效,工具类的作用是让使用的人快速完成某项活动,而不是让他惊叹三连。所以该有的注释还是要有的,如果大家对于注释感兴趣的人数足够多,我会开一期如何写注释的文章。

export class HttpRequest {
  private static instance: HttpRequest
  private constructor() {}
   /** 请求函数(单例模式)
   *
   * **注意:**
   * 1. 处理请求的函数记得使用`async/await`
   * 2. `method`需使用`HttpMethod`枚举类,切勿自行定义
   *
   * **示例代码**
   * ```js
    HttpRequest.getInstance().request({
      url: '/Api',
      method: HttpMethod.GET
    })
   * ```
   */
  public static getInstance(): HttpRequest {
    if (!this.instance) {
      this.instance = new HttpRequest()
    }
    return this.instance
  }
}

写到这里,单例模式就完成了,那么现在我们需要实现url的拼接。

处理url

HttpRequest中定义getApiAppName方法。此方法是兼容全路径写法的请求,在实际开发中,可以有一些非环境配置中心的请求需要处理,所以这里做一个兼容处理,同时也拼接好了全路径。

  // 处理url 兼容全路径
  private getApiAppName(url: string | undefined) {
    if (!url) {
      return
    }
    if (typeof url == 'string' && url.indexOf('http') >= 0) {
      return url
    }
    return `${Config.host}${url}`
  }

处理异常

HttpRequest中定义handerErrorStatus方法。此方法是用来处理后端异常状态码。

  // 处理后端异常状态码
  private handerErrorStatus(statusCode: number, requestConfig: RequestConfig) {
    if (statusCode == 502 || statusCode == 503) {
      if (requestConfig.needToken) {
        wx.showToast({
          title: '服务器开小差',
          icon: 'none'
        })
      }
      return true
    }
    return false
  }

定义核心方法request

HttpRequest中引入配置文件。

// 引入配置文件
const Config = require('../config/config')

接下来在HttpRequest中定义核心方法request,这里在处理状态码的时候采用的是if/else写法,如果大家业务里有更多的逻辑可以外面把规则定义成Map,然后内部引用规则,方便理解和阅读。

再次强调,一个好的代码是可以别人看懂而不是只有自己能看到,尤其是工具类方法,更要要降低理解门槛和使用门槛。

  public request(requestConfig: RequestConfig): Promise<SpecResult<MyAwesomeData>> {
    let _this = this
    //url处理
    requestConfig.url = this.getApiAppName(requestConfig.url)
    return new Promise((resolve, reject) => {
      // 默认header
      let header = {
        'content-type': 'application/json'
      }
      wx.request({
        method: requestConfig.method === HttpMethod.GET ? 'GET' : 'POST',
        url: `${requestConfig.url}`,
        data: requestConfig.data,
        header: Object.assign(header, requestConfig?.header),
        success: async function (res: SpecResult<MyAwesomeData>) {
          console.log('发送返回:', res) //res:{cookies, data, header, statusCode}
          if (_this.handerErrorStatus(res.statusCode, requestConfig)) {
            reject('失败了')
          } else {
            // 200状态码请求正常
            if (res.statusCode == 200) {
              if ([401, 402, 403, 405, 407, 408].indexOf(res.data.code) != -1 && requestConfig.needToken) {
                //……
              }
            } else {
              //非200状态码(排除handerErrorStatus特殊处理过的状态码)-数据处理
              res.data = { code: res.statusCode || -404, msg: '服务找不到', data:res.data }
            }
          }
          resolve(res)
        },
        fail: err => reject(err)
      })
    })
  }

恭喜你,到这里,封装已经完美结束了,那么我们一起来看下如何使用吧。

使用

  1. 在需要使用的文件内引入request
import { HttpMethod, HttpRequest } from '/resources/utils/request';
  1. 使用async/await接受返回值。
    const res = await HttpRequest.getInstance().request({
      url: 'serviceProduct/showcase/getShowcase',
      method: HttpMethod.GET
    })
  1. 可以开始业务逻辑啦!

写在最后

小程序这个系列我还会继续更新,敬请期待~

本篇文章到这里就结束了,如果文章对你有用,可以三连支持一下,如果文章中有错误或者说你有更好的见解,欢迎指正~

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐