TypeScript:如何使getCommandBuilder返回对应类型的命令构建器函数?
问题描述
我定义了名为ServiceType的枚举,每个枚举值对应一个服务,同时存在对应的配置接口和命令构建器函数——该函数接收配置接口实例并生成调用服务的命令对象。
我希望实现getCommandBuilder函数,接收枚举值后返回对应的命令构建器函数;但问题在于,TypeScript要求该构建器函数的参数必须实现所有配置接口,而非对应枚举值的专属配置接口。
示例代码如下:
const enum ServiceType { SERVICE_1, SERVICE_2, } interface Service1Config { field1: string; } interface Service2Config { field2: number; } const commandBuilder1 = (_: Service1Config) : void => {} const commandBuilder2 = (_: Service2Config) : void => {} const getCommandBuilder = (serviceType: ServiceType) => { switch (serviceType) { case ServiceType.SERVICE_1: return commandBuilder1 case ServiceType.SERVICE_2: return commandBuilder2 } } const myServiceType = ServiceType.SERVICE_1 const myServiceConfig = {field1: "Type one"} as Service1Config const myCommandBuilder = getCommandBuilder(myServiceType) myCommandBuilder(myServiceConfig) // ERROR!
(实际代码中会遍历包含所有服务类型和配置的数组,无法直接硬编码指定myCommandBuilder = commandBuilder1)
TypeScript给出的错误信息如下:
example.ts:32:18 - error TS2345: Argument of type 'Service1Config' is not assignable to parameter of type 'Service1Config & Service2Config'. Property 'field2' is missing in type 'Service1Config' but required in type 'Service2Config'.
如何让TypeScript识别:调用getCommandBuilder(ServiceType.SERVICE_1)返回的函数仅需Service1Config类型参数,调用getCommandBuilder(ServiceType.SERVICE_2)返回的函数仅需Service2Config类型参数?
解决方案
核心思路是通过类型映射+泛型约束,让getCommandBuilder的返回值类型与传入的枚举值强关联,而非返回所有构建器类型的交集。
基础实现方式
- 先定义服务类型到配置类型的映射,明确每个枚举值对应的配置:
// 建立服务类型与配置类型的一一映射 type ServiceConfigMap = { [ServiceType.SERVICE_1]: Service1Config; [ServiceType.SERVICE_2]: Service2Config; };
- 将
getCommandBuilder改为泛型函数,让TypeScript根据传入的枚举值推断返回的构建器参数类型:
const getCommandBuilder = <T extends ServiceType>(serviceType: T): (config: ServiceConfigMap[T]) => void => { switch (serviceType) { case ServiceType.SERVICE_1: return commandBuilder1 as (config: ServiceConfigMap[T]) => void; case ServiceType.SERVICE_2: return commandBuilder2 as (config: ServiceConfigMap[T]) => void; default: throw new Error(`未知服务类型: ${serviceType}`); } };
修改后,TypeScript会正确推断:
- 传入
ServiceType.SERVICE_1时,返回函数的参数类型为Service1Config - 传入
ServiceType.SERVICE_2时,返回函数的参数类型为Service2Config
测试验证:
const myServiceType = ServiceType.SERVICE_1 const myServiceConfig = {field1: "Type one"} as Service1Config const myCommandBuilder = getCommandBuilder(myServiceType) myCommandBuilder(myServiceConfig) // 不再报错 // SERVICE_2场景同样能正确约束 const service2Config = {field2: 123} as Service2Config const builder2 = getCommandBuilder(ServiceType.SERVICE_2) builder2(service2Config) // 通过类型检查
批量服务场景优化
如果服务数量较多,可进一步将构建器也纳入类型映射,让代码更易维护,也适配遍历服务数组的场景:
// 配置类型映射 type ServiceConfigMap = { [ServiceType.SERVICE_1]: Service1Config; [ServiceType.SERVICE_2]: Service2Config; }; // 自动推导构建器类型 type CommandBuilderMap = { [K in keyof ServiceConfigMap]: (config: ServiceConfigMap[K]) => void; }; // 构建器实例集合 const commandBuilders: CommandBuilderMap = { [ServiceType.SERVICE_1]: commandBuilder1, [ServiceType.SERVICE_2]: commandBuilder2, }; // 简化getCommandBuilder实现 const getCommandBuilder = <T extends ServiceType>(serviceType: T): CommandBuilderMap[T] => { return commandBuilders[serviceType]; };
新增服务时,只需扩展ServiceConfigMap和commandBuilders即可,无需修改getCommandBuilder的核心逻辑。
内容的提问来源于stack exchange,提问作者ArifulH8
相关产品推荐
相关产品推荐

