基于洋葱模型的工程化网络请求库: @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 仓库

相关推荐
无心使然云中漫步27 分钟前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
Bug缔造者32 分钟前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_1 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js
麒麟而非淇淋2 小时前
AJAX 入门 day1
前端·javascript·ajax
2401_858120532 小时前
深入理解MATLAB中的事件处理机制
前端·javascript·matlab
阿树梢2 小时前
【Vue】VueRouter路由
前端·javascript·vue.js
随笔写3 小时前
vue使用关于speak-tss插件的详细介绍
前端·javascript·vue.js
快乐牌刀片884 小时前
web - JavaScript
开发语言·前端·javascript
秋雨凉人心4 小时前
call,apply,bind在实际工作中可以使用的场景
javascript
哪 吒5 小时前
华为OD机试 - 第 K 个字母在原来字符串的索引(Python/JS/C/C++ 2024 E卷 100分)
javascript·python·华为od