经验总结:typescript 和 axios 项目中大量接口该如何管理和组织
引言
本文旨在介绍一种方法,用于在 typescript 和 axios 的项目中,有效的组合和管理大量的 API 接口以及 interface。
假如我们根据 API 文档对所有的接口做了初步分类,大体如下:
scm(某业务模块)
├── inventory(库存业务)
│ ├── warehouse # 仓库资源
│ ├── material # 物料资源
│ ├── unit # 计量单位资源
│ └── ...
├── order(订单业务)
│ ├── order # 订单资源
│ ├── customer # 客户资源
│ └── ...
└── ...
具体来说,对于单个但资源后端提供了一系列 CRUD 操作接口, 以warehouse
为例:
类型 | 方法&路径 |
---|---|
列表 | <GET /api/scm/warehouse/> |
创建 | <POST /api/scm/warehouse/> |
详情 | <GET /api/scm/warehouse/1/> |
整体更新 | <PUT /api/scm/warehouse/1/> |
部分更新 | <PATCH /api/scm/warehouse/1/> |
删除 | <DELETE /api/scm/warehouse/1/> |
在项目中,我们会有大量的资源,每种资源又会产生很多接口,对接这些接口我们需要创建很多请求函数,因此如何管理这些请求函数变得尤为重要。本文将介绍一种高效的组织方法,话不多说,让我们开始吧。
一、封装 axios
我们不讨论对axios
的深度封装,这里只做最简单的处理:
// utils/request.ts
import { useTokenStore } from "@/stores/auth/token";
import axios from "axios";
// 后端API统一的返回结构
export type Result<T> = {
status: string;
code: number;
message: string[];
result: T;
};
const tokenStore = useTokenStore();
const access = tokenStore.getToken.access;
// 配置新建一个 axios 实例
const instance = axios.create({
baseURL: import.meta.env.VITE_API_URL || "http://localhost:8000",
timeout: 60000,
});
// 添加请求拦截器
instance.interceptors.request.use(
function (config) {
// 添加token
if (access) {
config.headers.Authorization = `Bearer ${access}`;
}
return config;
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 添加响应拦截器
instance.interceptors.response.use(
function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response;
},
function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
}
);
export default instance;
二、对接接口
在引言中我们介绍了所有的资源接口,以及其分类:
scm(某业务模块)
├── inventory(库存业务)
│ ├── warehouse # 仓库资源
│ ├── material # 物料资源
│ ├── unit # 计量单位资源
│ └── ...
├── order(订单业务)
│ ├── order # 订单资源
│ ├── customer # 客户资源
│ └── ...
└── ...
根据分类,我们创建下面的目录接口来存放请求函数:
/vue-project
├── /src
│ └── /api
│ ├── ...
│ └── /scm
│ ├── inventory.ts (示例所在)
│ ├── order.ts
│ ├── ...
│ └── index.ts
以warehouse
资源为例,我们要为其所有的资源操作接口创建请求方法、要为其所有接口的请求体和响应体创建interface
:
// .../scm/inventory.ts
import request, { type Result } from "@/utils/request";
const baseUrl = "/api/scm";
// 仓库接口
// ----------------------------------------------------
export interface WarehouseBaseIn {
name: string;
code: string;
}
export interface WarehouseBaseOut {
id: number;
name: string;
code: string;
created_by: number;
created_at: string;
}
export interface WarehousePatchIn {
name?: string;
code?: string;
}
export const warehouse = {
list: () =>
// 获取列表
request<Result<WarehouseBaseOut[]>>({
url: baseUrl + "/warehouse/",
method: "GET",
}),
create: (data: WarehouseBaseIn) =>
// 创建
request<Result<WarehouseBaseOut[]>>({
url: baseUrl + "/warehouse/",
method: "POST",
data,
}),
retrieve: (id: string) =>
// 按ID查询
request<Result<WarehouseBaseOut[]>>({
url: baseUrl + `/warehouse/${id}/`,
method: "GET",
}),
put: (id: string, data: WarehouseBaseIn) =>
// 全部更新
request<Result<WarehouseBaseOut[]>>({
url: baseUrl + `/warehouse/${id}/`,
method: "PUT",
data,
}),
patch: (id: string, data: WarehousePatchIn) =>
// 部分更新
request<Result<WarehouseBaseOut[]>>({
url: baseUrl + `/warehouse/${id}/`,
method: "PATCH",
data,
}),
destroy: (id: string) =>
// 删除
request<Result<any>>({
url: baseUrl + `/warehouse/${id}/`,
method: "Delete",
}),
};
// 除了warehouse之外,inventory文件中实际上还会有更多的接口。
// 比如: material、unit等等,封装的方法都是类似的。
// ...
对单个资源的封装可以总结为如下结构:
const source = {
list: function...,
create: function...,
retrieve: function...,
put: function...,
patch: function...,
destroy: function...
}
回顾我们的目录结构:
/vue-project
├── /src
│ └── /api
│ ├── ...
│ └── /scm
│ ├── inventory.ts (示例所在)
│ ├── order.ts
│ ├── ...
│ └── index.ts
上述的目录结构,使得我们可以对大量的资源进行分类组织,而为了方便请求函数调用,我们还需要在index.ts
中对请求函数进行汇总。
// .../scm/index.ts
import { warehouse, material, unit,... } from './inventory'
import { order, customer, ... } from './order'
const api = {
inventory: {
warehouse,
material,
unit,
...
},
order: {
order,
customer,
...
}
}
export default api
使用方法如下,并且能够充分得到 ide 提示:
import api from "@/api/scm";
// 使用.时ide的提示会很友好
api.inventory.warehouse.list();
api.inventory.material.list();
api.order.customer.list();
api.order.order.list();
总结
对资源接口做好分类,将请求函数组织在合理的目录结构中,有助于提高代码的可维护性和可扩展性。同时对请求函数进行统一封装和组织,使得对请求的调用更加方便。