基于洋葱模型的工程化网络请求库: @bolt/api

不要轻视简单,简单意味着坚固。

-- 『三体』

为什么需要接口定义层

这里是一段 axios 请求调用

TypeScript 复制代码
  axios.post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

缺少 ts 类型提示, 我们通常喜欢创建一个 apis 文件夹做一层函数封装:

  • 对函数的入参和返回值做类型定义

  • 如果需要对 request 或者 response 处理, 也可以在这个函数里实现

如果一类接口有相同的配置, 如 baseURL , 我们通常会

TypeScript 复制代码
const instance = axios.create({
  baseURL: 'https://some-domain.com/api/',
  timeout: 1000,
  headers: {'X-Custom-Header': 'foobar'}
});

对 request 或者 response 做统一处理, 可以用 instance 的 interceptors.

通常对 request 或者 response 的处理并不适用全部接口, 我们可能会把它抽象成自定义 config, 就可以在 interceptors 里做判断按不同场景处理.

怎样才算好的工程化设计

软件工程应用一种系统的、规范的、可量化的方法来开发、操作和维护软件,并研究这些方法。

-- segmentfault.com/a/119000002...

基于高阶函数的接口定义

你是怎么想的, 代码就是什么样的

接口调用层

假如我们想要一个 getDetail(params, option): Promise<Detail> 接口在业务代码中调用.

在代码中使用就是:

TypeScript 复制代码
const detail = await getDetail({ id: 'xxx' }, option);

接口定义层

我们来设计一个接口定义层:

TypeScript 复制代码
export const getDetail =
    createApi<Request, Response>('/api/getDetail', createOption);

createApi 就是创建一个接口, 只需要定义一下 path, 然后用 ts 的函数泛型来指定参数和返回值的类型, 接口调用时传的 option 如果在大多数情况下相同, 那么就可以放在 createOption 里;

如果定义的多个接口中, createOption 相同, 那么我们需要一个请求的 instance

TypeScript 复制代码
export const { createApi, use } = createInstance(instanceOption);

如 baseUrl, 可以在 instanceOption 中使用.

我们可以通过 use 方法来集成中间件

TypeScript 复制代码
/** 请求前后打日志 */
use(logger);
/**
 * 静默登录&重登中间件
 */
use(
  createLoginMiddleware({
    checkLoginError,
  })
);
/**
 * 对后端错误码分类, 抛出各类错误
 * 中间件必须放在最后一位
 */
use(unwrap);

基于后端接口定义我们可以通过一些工具生成前端 ts 类型.

简单的 @bolt/api 原理

基于 @bolt/compose, 参考 <静默重登>为例, 介绍<洋葱模型中间件>

TypeScript 复制代码
import { compose } from "@bolt/compose"; // 见上述文档

export const createInstance = <T>(
  instanceOption: ApiOption & Partial<T>
): ApiInstance<Partial<T>> => {
  const middlewareArr: ApiMiddleware<unknown>[] = [];
  let run: (
    option: ApiOption & Partial<T>,
    next: (option: ApiOption) => Promise<unknown>
  ) => Promise<any>;
  return {
    createApi(path, defineOption) {
      if (!run) {
        run = compose(middlewareArr);
      }
      return (data, _option) => {
        const option = {
          method: ApiMethod.POST,
          path,
          ...initOption,
          ...instanceOption,
          ...defineOption,
          ..._option,
          data,
        };
        if (option.method === ApiMethod.GET) {
          option.query = {
            ...option.query,
            ...option.data,
          };
        }
        option.middleware = option.middleware || autoMiddleware;
        return option.middleware(option, () => run(option, option.requester));
      };
    },
    use(middleware) {
      middlewareArr.push(middleware);
    },
  };
};

@bolt/api 解决了什么问题

  • 代码体积小

  • 洋葱模型的中间件逻辑内聚

  • es module 导出函数, 支持 webpack tree-shaking

  • 标准的工程化实践, 使用中间件和定义接口的代码整洁不冗长

更重要的是:

基于 ts 类型提示的中间件自定义配置可追溯

推荐项目开启 ts 类型的强制校验

@bolt/api 仅提供最基础的中间件, 推荐业务方根据自身情况实现自己的中间件.

中间件的能力范围取决于已存在的 Option, @bolt/api 通过规范 ts 类型来扩展自定义 Option.

这里是定义一个 instance 的 完整代码.

TypeScript 复制代码
interface CustomOption extends LoginNeedOption, UnwrapOption {}

const { createApi, use } = createInstance<CustomOption>({
  basePath: "/approval/web",
  needLogin: true,
});

use(trace);
use(logger);
use<LoginNeedOption>(
  createLoginMiddleware({
    checkLoginError,
  })
);
use(tracePureApi);
use<UnwrapOption>(unwrap);
创建 instance 时通过泛型约束中间件可以用的所有自定义 Option 强制约束中间件声明所用到的 Option 字段 创建 api 时提示所有可选 Option
一个文件了解所需内容, 预防中间件配置项重名 否则, 判断 option.needLogin 时就会提示类型错误 VSCode 可以通过 ts 快速定位到类型定义

声明: 本文说提到的 @bolt/api 为虚构, 并未发布到 npm 仓库

相关推荐
小五Five几秒前
TypeScript项目中Axios的封装
开发语言·前端·javascript
临枫5411 分钟前
Nuxt3封装网络请求 useFetch & $fetch
前端·javascript·vue.js·typescript
酷酷的威朗普2 分钟前
医院绩效考核系统
javascript·css·vue.js·typescript·node.js·echarts·html5
前端每日三省2 分钟前
面试题-TS(八):什么是装饰器(decorators)?如何在 TypeScript 中使用它们?
开发语言·前端·javascript
小刺猬_9853 分钟前
(超详细)数组方法 ——— splice( )
前端·javascript·typescript
契机再现4 分钟前
babel与AST
javascript·webpack·typescript
渊兮兮4 分钟前
Vue3 + TypeScript +动画,实现动态登陆页面
前端·javascript·css·typescript·动画
鑫宝Code4 分钟前
【TS】TypeScript中的接口(Interface):对象类型的强大工具
前端·javascript·typescript
和你一起去月球5 分钟前
TypeScript - 函数(下)
javascript·git·typescript
_Legend_King11 分钟前
vue3 + elementPlus 日期时间选择器禁用未来及过去时间
javascript·vue.js·elementui