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

相关推荐
CodeClimb1 小时前
【华为OD-E卷 - 第k个排列 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
光头程序员4 小时前
grid 布局react组件可以循数据自定义渲染某个数据 ,或插入某些数据在某个索引下
javascript·react.js·ecmascript
fmdpenny5 小时前
Vue3初学之商品的增,删,改功能
开发语言·javascript·vue.js
小美的打工日记5 小时前
ES6+新特性,var、let 和 const 的区别
前端·javascript·es6
涔溪5 小时前
有哪些常见的 Vue 错误?
前端·javascript·vue.js
程序猿online5 小时前
前端jquery 实现文本框输入出现自动补全提示功能
前端·javascript·jquery
Turtle7 小时前
SPA路由的实现原理
前端·javascript
HsuYang8 小时前
Vite源码学习(九)——DEV流程中的核心类(下)
前端·javascript·架构
傻小胖8 小时前
React 中hooks之useInsertionEffect用法总结
前端·javascript·react.js
蓝冰凌10 小时前
【整理】js逆向工程
javascript·js逆向