You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用TypeScript创建基于字符串字面量确定返回类型的类型安全泛型API SDK

TypeScript泛型API SDK:基于字符串字面量的类型安全实现

问题描述

希望用TypeScript创建一个泛型API SDK,让返回类型由传入的字符串字面量参数决定。同时基于字符串到对象类型的预设映射,实现:

  • a) 确定返回值的类型
  • b) 确保函数具备最高程度的类型安全性

示例代码

import Axios from "axios";

export const axios = Axios.create();

type Foo = {
    id: string;
    name: string;
    foo: number;
}

type Bar = {
    id: string;
    name: string;
    bar: number;
}

type FooResponse = {
  response: Foo[];
}

type BarResponse = {
  response: Bar[];
}

type FooBarMap = {
  foo: FooResponse;
  bar: BarResponse;
}


const getFooBar = async <T extends keyof FooBarMap>(endpoint: T, query: Record<string, any> = {}) => {
  const records: FooBarMap[T]["response"] = [];

  const response = await axios.get<FooBarMap[T]>(`/${endpoint}`, query);

  const data = response.data.response;

  records.push(...data);

  return records;
}

遇到的错误

类型为'Foo | Bar'的参数无法赋值给类型为'Foo & Bar'的参数。
类型'Foo'无法赋值给类型为'Foo & Bar'。
类型'Foo'中缺少类型'Bar'所需的属性'bar'。

解决方案

错误原因

TypeScript处理泛型数组的push操作时,无法精准推断data的具体类型是Foo[]还是Bar[],而是将其推断为(Foo | Bar)[]。但records的类型是FooBarMap[T]["response"](即T为foo时是Foo[],为bar时是 Hotels获取充态nett现将体现[具体ossJava classic Acceler drug](https://github.com/microsoft/TypeScript/issues/30655),两种类型无法直接兼容,因此报错。

修复代码

方案1:用类型断言明确data类型

const getFooBar = async <T extends keyof FooBarMap>(endpoint: T, query: Record<string, any> = {}) => {
  const records: FooBarMap[T]["response"] = [];

  // 注意:axios.get的第二个参数是配置对象,query需放在params字段中
  const response = await axios.get<FooBarMap[T]>(`/${endpoint}`, { params: query });

  // 断言data类型与records完全匹配
  const data = response.data.response as FooBarMap[T]["response"];

  records.push(...data);

  return records;
}

方案2:简化逻辑直接返回响应数据

如果不需要额外的数组拼接逻辑,直接返回响应数据可彻底避免类型推断问题:

const getFooBar = async <T extends keyof FooBarMap>(endpoint: T, query: Record<string, any> = {}) => {
  const response = await axios.get<FooBarMap[T]>(`/${endpoint}`, { params: query });
  return response.data.response;
}

提升类型安全性的优化点

  • 严格约束Query类型:为每个端点定义专属的Query类型,避免传参错误:

    // 为每个endpoint绑定对应的Query类型
    type EndpointQueryMap = {
      foo: { page?: number; limit?: number };
      bar: { category?: string };
    }
    
    const getFooBar = async <T extends keyof FooBarMap>(
      endpoint: T,
      query: EndpointQueryMap[T] = {} as EndpointQueryMap[T]
    ) => {
      const response = await axios.get<FooBarMap[T]>(`/${endpoint}`, { params: query });
      return response.data.response;
    }
    

    调用时传入不符合端点要求的Query会直接触发编译报错。

  • 避免any类型:替换Record<string, any>为具体类型或unknown,配合类型守卫进一步校验数据。

  • 显式声明返回类型:让函数类型更清晰,便于维护:

    const getFooBar = async <T extends keyof FooBarMap>(
      endpoint: T,
      query: EndpointQueryMap[T] = {} as EndpointQueryMap[T]
    ): Promise<FooBarMap[T]["response"]> => {
      const response = await axios.get<FooBarMap[T]>(`/${endpoint}`, { params: query });
      return response.data.response;
    }
    

问题解答

  • a) 确定返回值类型:通过FooBarMap[T]["response"]的索引访问类型,完全可以根据传入的endpoint字符串字面量确定返回值的具体类型。比如调用getFooBar('foo')时,返回类型会被推断为Promise<Foo[]>;调用getFooBar('bar')时则为Promise<Bar[]>。

  • b) 最高程度的类型安全性:通过约束endpoint为keyof FooBarMap、绑定端点专属Query类型、避免any、显式类型声明等优化,可在编译阶段捕获大部分类型错误(如传入不存在的 lent随意 fileH一组...)excel yet优秀画面 graveZ栓蛇),比如无效端点、错误参数、返回值误用等,实现高度的类型安全。

内容的提问来源于stack exchange,提问作者Tyler Clendenin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 20:42:04