@siyuan0215/easier-axios-dsl
TypeScript icon, indicating that this package has built-in type declarations

1.2.3 • Public • Published

@siyuan0215/easier-axios-dsl

基于 Axios 以及借鉴了简单的 DSL(领域特定语言)制定了一套基本规则用于接口对接,减少冗余、重复的接口声明模板代码,提高开发效率。

🤖 如何安装?

使用 NPM 安装:

npm i @siyuan0215/easier-axios-dsl

使用 Yarn 安装:

yarn add @siyuan0215/easier-axios-dsl

🤙 优势

未使用 easier-axios-dsl 之前,我们再对接接口的时候往往需要添加如下代码:

// api/index.ts
import { request } from "@/utils/request";

export const getUserInfo = (userId: string) =>
  request("api/getUserInfo", {
    method: "GET",
    params: { userId },
  });

export const getPosts = ({ pageNumber, pageSize }) =>
  request("api/getPosts", {
    method: "GET",
    params: {
      pageNumber,
      pageSize,
    },
  });

export const saveUserInfo = (userInfo: UserInfo) =>
  request("api/saveUserInfo", {
    method: "POST",
    data: userInfo,
  });

可以看出,在声明接口时有很多重复的模板代码,这种方式主要有两个痛点:

  • 接口较多时,大量的模板代码导致的冗余的代码增加,前端资源体积会增大;
  • 影响了开发小伙伴的开发效率;

easier-axios-dsl 利用简单的“特定领域语言”优化了上述的两个痛点,一定程度上提高了开发的效率和减少了冗余的代码。

对于上述的代码例子,使用 easier-axios-dsl 进行改造:

// api/index.ts
import { generatorAPIS } from "@/utils/generateAPIs";

enum APIS {
  getUserInfo = "GET api/getUserInfo q:userId",
  getPosts = "GET api/getPosts d:pageNumber,pageSize",
  saveUserInfo = "POST api/saveUserInfo d:*",
}

export default generatorAPIS<typeof APIS>(APIS);

可以明显看出,使用 easier-axios-dsl 对接接口时 coding 更顺畅也更简洁。

默认导出的 generatorAPIS<typeof APIS>(APIS); 函数执行结果是对象。

需要注意的是,接口对象是以我们声明的接口的名字作为 key(比如 getUserInfo),以 Promise 作为值。因此我们既可以使用 async / await 也可以使用 Promise 处理请求的回调。

⚙️ 如何静态(全局)配置?

easier-axios-dsl 基于 Axios 进行二次封装。

考虑到可扩展性,我们提供了 requestCreator(options: AxiosRequestConfig & Options<T>) 方法需要您自行创建一个 Axios 的实例,并允许我们对其进行静态地配置。

// utils/generateAPIs.ts

import { requestCreator, G } from "@siyuan0215/easier-axios-dsl";

const otherAxiosConfig = {
  timeout: TIMEOUT.DEFAULT,
  withCredentials: true,
};

// 创建一个 Axios 的实例,参数为静态配置
export const request = requestCreator({
  requestInterceptors: [
    /* ... */
  ],
  responseInterceptors: [
    /* ... */
  ],
  ...otherAxiosConfig,
});

// 当前版本下,此具名导出必不可少
export const generatorAPIS = <T extends {}>(apiConfig: T) =>
  G<T>(request, apiConfig);

❗️ 请注意,看上去上述代码有些繁琐,本可以将静态配置传入 G 函数中,直接返回 generatorAPIS 函数即可。但是考虑到在项目中可能有些接口对接需要用到 Axios 实例,因此我们决定将其暴露出来。

requestCreator 函数返回的请求实例 request 函数参数类型声明如下:

request(
  { method, url, params, ...restOptions }: {
      url: AxiosRequestConfig["url"];
      method: AxiosRequestConfig["method"];
      params: AxiosRequestConfig["params"] | AxiosRequestConfig["data"];
    } & Omit<AxiosRequestConfig<any>, "url" | "method" | "params" | "data">,
  // 是否将 body 体封装为 formData
  isFormData?: boolean | undefined): Promise<T>

request 函数中接收传递请求体数据的 key 是 params,不论是 POST 还是 GET 请求方式,均通过这个字段传递。

我们保(tou)留(lan)了 Axios 的配置,同时提供了如下配置项:

export type Options<T> = {
  // 请求拦截器
  requestInterceptors: [OnFulfilled<AxiosRequestConfig>, OnRejected];
  // 响应拦截器
  responseInterceptors: [OnFulfilled<AxiosResponse<T>>, OnRejected];
};
参数 类型 是否必填 说明
requestInterceptors [OnFulfilled<AxiosRequestConfig>, OnRejected] 请求拦截器
responseInterceptors [OnFulfilled<AxiosResponse>,OnRejected] 响应拦截器

拦截器的具体配置方法参照官方文档

❗️ 需要注意的是,使用 requestCreator 创建后的 Axios 实例会丢失官方的请求拦截和响应拦截的静态配置方式,需要使用 requestInterceptorsresponseInterceptors 配置项,同时也无法使用多拦截器的特性。

🧑🏽‍💻 如何使用?

继续以上述改造后的接口声明文件为例,我们创建了如下三个接口的函数(服务):

  • getUserInfo
  • getPosts
  • saveUserInfo

只需要我们在对应的业务组件中,引入我们的接口函数,调用即可。

<script lang="ts">
import APIS from '@/api/index.ts';

const fetchUserInfo = async (userId: string) => {
  try {
    const { data } = await APIS.getUserInfo({ userId });
    // ...
  } catch(error) {
    console.log("getUserInfo =>", error)
  }
}
</script>

接口函数中的类型如下:

type ApiRequestUrlMethod = (
  majorPayload: Record<string, any> | any[],
  otherPayload?: Record<string, any>,
  AxiosConfig?: AxiosRequestConfig
) => Promise;

其中:

  • majorPayload:既可以是对象,也可以是数组(取决于后端声明 POST 请求的 body 体类型);
  • otherPayload:对象,可选,仅当 body 体需要传递数组,且接口仍需要传递额外的参数时,将额外的参数传入此对象中;
  • AxiosConfig:对象,可选,Axios 运行时配置;

❗️ 如果需要给请求添加运行时配置 AxiosConfig,又不需要用到 otherPayload(body 体非数组),请将第二个参数显示地传入 undefinedvoid 0,例如:

APIS.exportFile(params, undefined, { responseType: "blob" });
// or
APIS.exportFile(params, void 0, { responseType: "blob" });

🔌 如何新增接口声明?

新声明一个接口时,需要遵照如下格式,每个部分(以 [] 内为一部分)均以空格隔开:

[POST|GET|PUT|DELETE|...] [url] [(d|data)|(d.f|d.formData|data.f|data.formData)|([d]|[data]))|(q|query)|path]:[(id,name,gender,...)|'*']

其中:

  • POST|GET|PUT|DELETE|... HTTP 的请求方式;
  • url 请求地址;
  • 请求参数,格式为 [参数载体类型]:[传递的参数 key],请注意参数载体类型需要传递的参数需要以 : 冒号相连(不能出现空格)
    • (d|data)|(d.f|d.formData|data.f|data.formData)|([d]|[data])) 对应 POSTPUT 请求的 Body 体,其中:
      • ddata 指将参数放到正常 Body 体中(对象类型);
      • d.fd.formDatadata.fdata.formData 指需要传递的是 FormData 类型,但是调用接口函数时传递对象类型(内部会帮助你处理好);
      • [d|data] 指 Body 体传递的是数组,等价于:data: [1,2,3,4]
    • q|query 传递给 GETDELETE 请求的 query 参数;
    • path 表示将参数放到请求地址中进行传递(动态路由),比如:api/getUserInfo/{uesrId}
  • [(id,name,gender,...)|'*'] 参数的 key
    • 枚举,如果有明确的 key,那么请以 , 逗号相连(不能出现空格),且最终请求后端接口时所传递的参数以此处声明的 key 为准;
    • 通配符 * 如果需要传递的参数比较多,可以使用 * 来表示将所有的参数全部传给后端(跳过参数校验的阶段);

我们以 GETPOST 请求举几个例子,帮助大家理解:

/**
 * POST:
 *    - `POST posts/save d:*`;
 *        equal: (params) => api.post({ url: baseUrl + 'posts/save', params }, true)
 *
 *    - `POST upload-file d:sourceType,systemType,fileName,file,remark`;
 *        equal: (types) => api.post({ url: baseUrl + 'upload-file', data: { file, remark } })
 *
 *    - `POST posts/list q:pageNumber,pageSize`;
 *        equal: (pageNumber, pageSize) => api.post({ url: baseUrl + 'posts/list', params: { pageNumber, pageSize} })
 *
 *    - `POST users/search [d] q:a`
 *        equal: (types) => api.post({ url: baseUrl + 'users/search' + '?a=1', data: types })
 *
 *    - `POST users/update d.f:a,b,c`
 *        equal: (data) => api.post({ url: baseUrl + 'users/update', data: FormData<{ a, b ,c }> }) and Content-Type is 'multipart/form-data'
 *
 * GET:
 *    - `GET users/getUserInfo q:userId`
 *        equal: (userId: string) => api.get({ url: baseUrl + 'users/getUserInfo', params: { userId } })
 *
 *    - `GET users/get/{id} path:id`
 *        equal: (id: string) => api.get({ url: baseUrl + 'users/get/' + id })
 * */

👻 单元测试

尽可能的覆盖所有的代码,保证代码的质量。

alt text

📦 未来计划?

  • [X] 单元测试的编写;
  • [ ] 支持多拦截器;
  • [ ] 支持更多的载体类型,比如 Map 等;
  • [ ] 支持原生小程序;

Readme

Keywords

Package Sidebar

Install

npm i @siyuan0215/easier-axios-dsl

Weekly Downloads

67

Version

1.2.3

License

MIT

Unpacked Size

20.1 kB

Total Files

7

Last publish

Collaborators

  • siyuan0215