首页 > 解决方案 > 带有 Typescript 和 Axios 的通用 CRUD API 客户端

问题描述

我正在尝试使用 Axios 在 Typescript 中为我的 API 实现通用 CRUD 接口。

假设 API 公开了使用 2 个实体执行 CRUD 操作的端点。假设端点是/users并且/posts/都采用 URL 参数offsetcount(用于分页)。响应示例:

GET /users?offset=2&count=2

{
  "users": ["user1", "user2"],
  "total": 4 // the total number of users that exist
}

类似地posts,帖子数组的键是“帖子”。

我试图创建一个通用函数来获取用户/帖子,如下所示:

export const getPage =
  async <T extends Persistable>(offset: number, count: number): Promise<PaginatedResponse<T>> => {
    const response = await axios.get<PaginatedResponse<T>>(
      `${SOME_URL}/<BLANK>`,
      {
        params: { page, size },
        transformResponse: [
          (data) => ({
            ...data,
            items: data[<BLANK>],
          })
        ]
      },
    );
    return response.data;
  };

和接口Persistable都继承自和User接口的接口如下所示:PostPaginatedResponse

export interface PaginatedResponse<T> {
  readonly total: number;
  readonly items: T[];
}

基本上,我需要通过例如基于传递的 type<BLANK>以某种方式获取字符串"users"/来填写 s 。"posts"T

请问有人能想出一种方法来实现这一点吗?

标签: typescriptaxios

解决方案


我可能会建议使用函数重载来遵循解决方案。我不建议在这种情况下使用泛型,因为T它仅限于useror 或post. 由于 TypeScript 接口与形状有关,因此泛型T几乎可以是任何可以扩展的东西Persistable

我想象用户和帖子不会是字符串数组,而是对象数组。type属性将创建可区分的联合并在编译后保留。

我添加了额外的参数reqTypegetPage区分用户和帖子请求。reqType可能是'user''post'。其他值会导致错误。

User['type']索引访问运算符,因此如果您将来添加另一种类型的响应,reqType 将自动填充type值。

type User = { type: 'user', user: string };
type Post = { type: 'post', post: string };

export interface PaginatedResponse<T> {
    readonly total: number;
    readonly items: T[];
}

// This is type guard which will stay in run time
function reqTypeIsUser(a: User['type'] | Post['type']): a is User['type'] {
    return (a as User['type']) === 'user'
}

async function getPage
    (offset: number, count: number, reqType: User['type']): Promise<PaginatedResponse<User>>
async function getPage
    (offset: number, count: number, reqType: Post['type']): Promise<PaginatedResponse<Post>>
async function getPage
    (offset: number, count: number, reqType: User['type'] | Post['type']): Promise<PaginatedResponse<User | Post>> {

        const response = await axios.get<PaginatedResponse<User | Post>>(
            'url' + reqTypeIsUser(reqType) ? 'users' : 'posts',
            {
                params: { offset, count },
                transformResponse: [
                    (data) => ({
                        ...data,
                        items: data[reqType].map((d: string) => ({ type: reqType, [reqType]: d })),
                      })
                    ]
                  },
                );
                return response.data;
};

let user = getPage(1, 2, "user");  // user will be Promise<PaginatedResponse<User>>

推荐阅读