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

如何用字面量类型关联TypeScript函数的参数类型?

问题:实现类型安全的TypeScript API客户端

我有一个通用性较强的API,想要实现一个类型化的TypeScript客户端。目前我的代码如下:

export type EntityTypes =
    | 'account'
    | 'organization'
    | 'group'

export function getListByValue<TEntityColumns>(
  entity: EntityTypes, 
  filterColumn: keyof TEntityColumns, 
  filterValue: any) {
      apiRequest({
         entityType: entity,
         filter: {
            column: filterColumn,
            value: filterValue,
            op: '='
         }
      });
  }

该代码能运行,但存在以下问题:

  • 每次调用都需要手动指定泛型参数;
  • 泛型参数并非必填(虽可规避但实现不够优雅);
  • 可能意外出现泛型参数与实体类型字面量参数不匹配的情况。

是否可以将TEntityColumns类型与EntityTypes绑定到一个结构中,让API具备类型安全性,以最少的输入获得最大程度的自动补全和类型安全?

更新说明

我正在寻找通用解决方案,即如何将字面量类型与类型选择结合,因此未提供当前TEntityColumns的定义,原因如下:

  1. 该定义十分复杂;
  2. 它属于内部SDK的一部分,我无法修改。

目前使用现有定义,我可以编写如下代码:

getListByValue<AccountColumns>('account', 'name', 'John');

// 我可以省略泛型参数,但会失去类型安全
getListByValue('account', 'name', 'John');

// 我还能写出这样的代码,但编译器不会报错
getListByValue<OrganizationColumns>('account', 'VAT number', '123456')

我的期望是实现如下调用方式(或其他可行方案):

// 省略泛型参数
getListByValue('account', 'name', 'John');

// 或仅指定泛型参数
getListByValue<AccountColumns>('name', 'John')

附现有相关类型定义

export declare type EntityColumns<T extends {
    [name: string]: ColumnDef;
}> = {
    [P in keyof T]: EntityColumnValue<T[P]>;
};

export declare type EntityColumnValue<T extends ColumnDef> = 
    T['type'] extends 'boolean' ? boolean | undefined : 
            T['type'] extends 'number' ? number | undefined : 
                    T['type'] extends 'number-array' ? number[] | undefined : ...

export declare type ColumnDef = {
    type: 'boolean';
    index?: boolean;
} | {
    type: 'number';
    index?: boolean | 'unique';
} | {
    type: 'number-array';
    index: boolean;
} | ...
解决方案

方案1:建立实体-列类型映射表,自动推导类型

首先,创建一个实体类型到对应列类型的映射类型,把EntityTypes的每个字面量和对应的AccountColumns、OrganizationColumns等绑定起来:

// 替换为你实际已有的列类型定义
type AccountColumns = EntityColumns<{ name: { type: 'string' }, age: { type: 'number' } }>;
type OrganizationColumns = EntityColumns<{ 'VAT number': { type: 'string' }, size: { type: 'number' } }>;
type GroupColumns = EntityColumns<{ label: { type: 'string' }, members: { type: 'number-array' } }>;

// 核心:实体与列类型的绑定映射
type EntityToColumns = {
  account: AccountColumns;
  organization: OrganizationColumns;
  group: GroupColumns;
};

修改getListByValue的函数签名,利用映射表让TypeScript自动根据传入的entity参数推导对应列类型,同时严格限制参数匹配:

export function getListByValue<T extends EntityTypes>(
  entity: T,
  filterColumn: keyof EntityToColumns[T],
  filterValue: EntityToColumns[T][typeof filterColumn]
) {
  apiRequest({
    entityType: entity,
    filter: {
      column: filterColumn,
      value: filterValue,
      op: '='
    }
  });
}

// 调用示例(自动补全+类型安全)
getListByValue('account', 'name', 'John'); // 正常通过
getListByValue('account', 'age', 'John'); // 报错:age列类型为number,不能传字符串
getListByValue('organization', 'name', 'John'); // 报错:organization不存在name列

方案2:支持仅指定泛型参数的调用方式

如果需要支持仅传泛型、省略实体字面量的调用,可以通过函数重载+反向映射实现:

// 反向映射:从列类型获取对应的实体类型
type ColumnsToEntity<T> = {
  [K in keyof EntityToColumns]: EntityToColumns[K] extends T ? K : never;
}[keyof EntityToColumns];

// 函数重载:第一种(省略泛型,传入实体+列+值)
export function getListByValue<T extends EntityTypes>(
  entity: T,
  filterColumn: keyof EntityToColumns[T],
  filterValue: EntityToColumns[T][typeof filterColumn]
): void;

// 函数重载:第二种(仅指定泛型,传入列+值)
export function getListByValue<T extends EntityToColumns[keyof EntityToColumns]>(
  filterColumn: keyof T,
  filterValue: T[typeof filterColumn]
): void;

// 函数实现
export function getListByValue(
  arg1: EntityTypes | string,
  arg2?: string | any,
  arg3?: any
) {
  let entity: EntityTypes;
  let filterColumn: string;
  let filterValue: any;

  // 处理两种调用参数格式
  if (arg2 === undefined) {
    // 第二种调用:需要运行时映射表(类型信息编译后会丢失)
    const columnsToEntityRuntime: Record<string, EntityTypes> = {
      AccountColumns: 'account',
      OrganizationColumns: 'organization',
      GroupColumns: 'group'
    };
    // 注意:这里需要根据实际列类型的名称匹配运行时映射,若无法获取则需调整实现逻辑
    entity = columnsToEntityRuntime[/* 泛型对应的列类型名称,需自行补充 */];
    filterColumn = arg1 as string;
    filterValue = arg2;
  } else {
    // 第一种调用:直接解构参数
    entity = arg1 as EntityTypes;
    filterColumn = arg2 as string;
    filterValue = arg3;
  }

  apiRequest({
    entityType: entity,
    filter: {
      column: filterColumn,
      value: filterValue,
      op: '='
    }
  });
}

// 调用示例
getListByValue<AccountColumns>('name', 'John'); // 支持该调用方式

方案说明

  • 方案1优先推荐:完全利用TypeScript类型推导,无需手动指定泛型,严格保证实体与列类型的匹配,调用简洁且安全。
  • 方案2存在局限性:由于TypeScript泛型信息在运行时会被擦除,需要额外的运行时映射表配合,实现复杂度更高。若不是必须支持仅传泛型的调用,建议优先使用方案1。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 08:36:07