TypeScript中使用switch语句实现泛型类型推断的方法
问题描述
我正尝试创建一个带有不同配置的泛型类型,这些配置是不同实现之间的唯一差异。以下是我的代码示例:
interface Generic<Config extends ConfigurationType> { configuration_type: Config configuration: TypeToConfigMap[Config] } class ConfigA { z = 1 } // empty classes make bad examples class ConfigB { y = 2 } class ConfigC { x = 3 } const enum ConfigurationType { a = 'a', b = 'b', c = 'c', } interface TypeToConfigMap { [ConfigurationType.a]: ConfigA, [ConfigurationType.b]: ConfigB, [ConfigurationType.c]: ConfigC, }
核心问题出在下面的函数中:
function test(generic: Generic<ConfigurationType>[]) { generic.forEach((generic) => { switch (generic.configuration_type) { case (ConfigurationType.a): generic.configuration.z; // 无法推断z属性 break; case (ConfigurationType.b): generic.configuration.y; // 无法推断y属性 break; case (ConfigurationType.c): generic.configuration.x; // 无法推断x属性 break; } }) }
switch语句中generic.configuration的类型无法被正确推断,我希望保持这种简洁写法,请问有没有办法让类型得到正确推断?
解决方法
问题根源在于Generic<ConfigurationType>会被TypeScript解析为泛型实例的合并类型,而非能触发自动类型窄化的判别联合类型。要实现switch中的类型推断,只需调整类型定义:
方案一:使用判别联合类型(推荐)
先定义包含所有可能情况的联合类型,再让函数接收该类型的数组:
// 生成包含所有配置实例的联合类型 type GenericInstance = { [K in ConfigurationType]: Generic<K> }[ConfigurationType] // 修改函数参数类型为联合类型数组 function test(generic: GenericInstance[]) { generic.forEach((item) => { switch (item.configuration_type) { case ConfigurationType.a: item.configuration.z; // 自动推断为ConfigA,z属性可用 break; case ConfigurationType.b: item.configuration.y; // 自动推断为ConfigB break; case ConfigurationType.c: item.configuration.x; // 自动推断为ConfigC break; } }) }
原理
{[K in ConfigurationType]: Generic<K>}[ConfigurationType]会遍历枚举的每个值,生成对应的Generic<K>类型,最终合并为Generic<'a'> | Generic<'b'> | Generic<'c'>。TypeScript识别到这是带有明确判别字段(configuration_type)的联合类型后,会在switch匹配时自动窄化对应成员的类型。
方案二:泛型函数+类型断言
如果不想额外定义联合类型,也可以用泛型函数配合手动断言,但这种方式不够优雅:
function test<T extends ConfigurationType>(generic: Generic<T>[]) { generic.forEach((item) => { switch (item.configuration_type) { case ConfigurationType.a: (item.configuration as ConfigA).z; break; case ConfigurationType.b: (item.configuration as ConfigB).y; break; case ConfigurationType.c: (item.configuration as ConfigC).x; break; } }) }
内容的提问来源于stack exchange,提问作者cryptoid
相关产品推荐
相关产品推荐

