TypeScript封装API,让代码编写柔润丝滑

2020-07-17T08:19:34.png

引言

干前端工作,大致离不开三大任务:切图,对接口,写页面逻辑。说到对接口,那肯定是离不开网络请求API的封装的。我将网络请求的封装模式大致分为三个派系:

  1. 无拘无束派 (只封装请求根地址,想咋请求就咋请求,最强的封装就是不封装)

  2. 拦截请求派 (使用一个拦截器配置请求行为和一些错误的拦截处理)

  3. 接口集成派(使用一个或多个文件,统一管理所有请求,约定不允许使用文件中未定义的接口)。

我属于第三个派系,首先介绍一下各派的风格

各派风格

无拘无束派在快速成型方面略有优势,自由度也相对较高,但是维护起来并不容易,遇到接口改版的时候一部小心就会遗漏。

img

拦截请求派属于较优雅的一个派系,没有太多多余的内容,剩下的内容都是为解决问题而生。一般使用一个第三方请求库(如axios,flyio等)完成封装。大致像是这样


import { FlyError, FlyResponse } from "flyio";

const Fly = require("flyio/dist/npm/wx");

let fly = new Fly();

// 配置请求根地址

fly.config.baseURL = process.env.VUE_APP_BASE_URL;

// 配置响应拦截器

fly.interceptors.response.use(

   // 如果请求成功,即请求状态码2xx

  (response: FlyResponse) => {

    // 并且操作成功

    if (response.data.success) {

       // 返回响应数据体

      return Promise.resolve(response.data);

    } else {

    // 请求成功,但是操作失败,提示后端返回的msg,并抛出错误

      uni.showToast({

        icon: "none",

        title: response.data.msg,

      });

      return Promise.reject(response.data);

    }

  },

  // 如果请求失败,即状态非2xx

  (err: FlyError) => {

    console.error(err);

    // 状态码为401,即跳转登录

    if (err.status === 401) {

      uni.reLaunch({

        url: "/pages/login",

      });

      return;

    }

    // 其他错误状态码,就先弹个框吧

    uni.showModal({

      title: err!.request!.url + "接口状态" + err.status,

      content: "错误原因:" + err.engine.response.msg,

    });

    return Promise.reject(err);

  }

);

然后使用的时候就直接采用第三方的请求,如fly.get(),fly.post

现在来介绍我所属的派系,我比上面更极端一点,除了封装请求拦截器暴露POST,GET外,还将所有的接口集中到一个API文件(太多了就按类型拆分,如user,shop,setting)

我的实现

基本请求文件http.ts,已引入上文提及的拦截器


function GET(url: string, params = {}): Promise<ApiResponse> {

  let config = {

    headers: {

      Authorization: store.state.token,

    }

  };

  return fly.get(url, params, config);

}

function POST(url: string, params = {}): Promise<ApiResponse> {

  let config = {

    headers: {

      Authorization: store.state.token,

    }

  };

  return fly.post(url, params, config);

}

export { GET, POST };

api.ts,负责集成Api


import { GET, POST } from "./http";

export default class Api {

  /**

   * 登录接口

   * @param params phone,password

   */

  login(params: object) {

    return POST("/user/login", params);

  }

  /**

   * 获取账户下所有店铺

   */

  getShop() {

    return POST("/shop/getShopListByUserId");

  }

  /**

   * 获取首页数据总览

   * @param params {shopId}

   */

  getOverview(params: {}) {

    return POST("/user/homepage/statistics", params);

  }

  /**

   * 根据店铺Id获取二级分类

   * @param params {shopId}

   */

  getCommodityType(params: {}) {

    return POST("/cnccommodity/commodity_type/by_shop", params);

  }

  /**

   * 分页获取商品列表

   */

  getCommodityList(params: {}) {

    return POST("/cnccommodity/commodity_by_page", params);

  }

}

然后将Api文件,挂载到Vue的原型链。


import API from "./plugins/fly/api";

Vue.prototype.$api = new API();

在组件中使用


 this.$api.login(this.loginForm).then((res) => {

 console.log(res)

 })

这样高度封装的好处是,任何一个接口要改内容,或者自定义功能,都可以只维护Api文件就行了。假设有一需求,要在获取商品之后提示用户”恭喜发财“,而这个接口又在多个页面中使用,我可以只做如下修改即可。


 /**

   * 分页获取商品列表

   */

  getCommodityList(params: {}) {

    showToast("恭喜发财")

    return POST("/cnccommodity/commodity_by_page", params);

  }

这是一个荒诞的需求,但是如果需求是部分请求超过1s就显示加载中呢?或者部分接口需要显示后端返回的msg呢?我可以这样做。

api.ts


updatePw(params: object) {

    return Post("/adminmanage/updatePassWard", params, true);

},

http.ts


export async function Post(api: string, params = {}, needToast = false) {

  let data = await axios.post(api, params);

  if (data.success && needToast) {

    Message.success(data.msg);

  }

  return data;

}

为了应对频繁更改的需求,我真是煞费苦心。

Typescript的加持

上面一直在说接口封装的事情,好像对Typescript只字未提。虽然上面都使用了typescript但是都只是铺垫,真正让代码编写柔润丝滑的是声明文件。

原型Api的声明

上面我们已经将api挂载到vue的原型,但是typescript的作用并未完全发挥。typescript有两个强大的作用,1. 减少代码出错率 2. 提高代码书写效率。要启用ts强大的语法提示功能,我们需要先写一个d.t文件。

vue-property.d.ts


import Vue from "vue";

import Api from "./plugins/fly/api";

declare module "vue/types/vue" {

  interface Vue {

    $api: Api;

  }

}

该文件的作用就是将Vue原型链上的$api类型设置为Api Class, 接下来我们来一起看看它的效果。

typescript推导api

Vs code贴心的语法提示,从注释提示到参数。无与伦比的代码护航能力几乎能让你无脑写请求。

响应结构体的声明

人不能满足现状,光有请求推导可还不够。我们还要让它推导响应结构体。假设项目后端返回的响应结构如下


{

    "code": 200,

    "success": true,

    "msg": "操作成功",

    "result": {

    }

}

我们再创建一个响应的d.ts文件

index.d.ts


interface ApiResponse {

  /**

   * code: 响应状态码

   */

  code: number;

  /**

   * success: 操作是否成功标准

   */

  success: boolean;

  /**

   * msg: 请求的附带信息

   */

  msg: string;

  /**

   * result: 请求返回结果

   */

  result: Object | any;

}

同时声明接口请求返回为ApiResponse


function GET(url: string, params = {}): Promise<ApiResponse> {

  let config = {

    headers: {

      Authorization: store.state.token,

    }

  };

  return fly.get(url, params, config);

}

function POST(url: string, params = {}): Promise<ApiResponse> {

  let config = {

    headers: {

      Authorization: store.state.token,

    }

  };

  return fly.post(url, params, config);

}

export { GET, POST };

看看语法提示效果

ts推导响应体

同时如果有分页,还可以声明分页的结构体。总之,声明文件写得好,效率提高绝对少不了。

后记

ts给我一种以前写C++的感觉,需要先写声明文件.h然后在写.cpp,这样做的好处是约束你的代码,让你的代码更规范。不过,缺点就是声明文件有种给自己找事的感觉,不过我依旧强烈建议在前端项目下使用ts。这在后期的维护是绝对有利的,而且也并非所有文件都需要写声明文件,要不要写声明文件,取决于你的实现方式。比如本文中的Api文件就没写声明文件,照样可以类型推导,语法提示。祝我早日实现无脑编码~

版权声明: 本文首发于 指尖魔法屋-TypeScript封装API,让代码编写柔润丝滑(https://blog.thinkmoon.cn/post/895_typescript%E5%B0%81%E8%A3%85api_%E8%AE%A9%E4%BB%A3%E7%A0%81%E7%BC%96%E5%86%99%E6%9F%94%E6%B6%A6%E4%B8%9D%E6%BB%91/) 转载或引用必须申明原指尖魔法屋来源及源地址!