TypeScript接口中type关联id类型无法自动推断的问题及解决
我定义了一个包含type和id属性的接口,其中id的类型依赖于type的取值,接口定义如下:
interface MyInterface<T extends keyof any, IDMAP extends TypeMap<T>> { type: T; id: IDMAP[T]; }
但遇到一个问题:当检查type字段的取值时,id的类型并未被自动推断出来。这是TypeScript的bug吗?应该如何修改才能让这类类型检查生效?
示例代码:
const testMethod = ( action: MyInterface<MyEnum, EnumTypeMap>, ) => { if (action.type === MyEnum.ONLY_NUMBER) { // 为何此处id未被推断为number类型? testInferrence(action.id); } };
这不是TypeScript的bug,而是你的接口定义方式导致TypeScript无法正确进行**可辨识联合类型(Discriminated Union)**的类型收缩。
MyInterface<MyEnum, EnumTypeMap>展开后是单一接口类型,而非多个子类型的联合。TypeScript无法从单一接口的type属性值反向推导id的具体类型——接口里的id被定义为EnumTypeMap[MyEnum],也就是所有可能类型的联合(number | string | boolean),即便判断了type的值,TypeScript也无法将这个联合类型收缩到对应子类型。
要让TypeScript能通过type字段推断id的类型,你需要把类型定义改成可辨识联合类型,而非泛型接口。具体有两种实现方式:
方式1:直接定义联合类型
先为每个枚举值对应的类型单独定义接口,再组合成联合类型:
enum MyEnum { ONLY_NUMBER = 'ONLY_NUMBER', ONLY_STRING = 'ONLY_STRING', ONLY_BOOLEAN = 'ONLY_BOOLEAN' } type EnumTypeMap = { [MyEnum.ONLY_NUMBER]: number; [MyEnum.ONLY_STRING]: string; [MyEnum.ONLY_BOOLEAN]: boolean; } // 生成每个枚举对应的子接口并组合成联合类型 type MyInterface = { [K in keyof EnumTypeMap]: { type: K; id: EnumTypeMap[K]; } }[keyof EnumTypeMap]; const testInferrence = (arg: number) => {}; const testMethod = (action: MyInterface) => { if (action.type === MyEnum.ONLY_NUMBER) { // 此处id会被正确推断为number类型 testInferrence(action.id); } };
方式2:保持泛型但生成联合类型
如果想保留泛型的灵活性,可以通过泛型工具类型生成联合类型:
type TypeMap<T extends keyof any> = Record<T, any>; // 利用映射类型生成联合类型 type MyInterface<T extends keyof IDMAP, IDMAP extends TypeMap<T>> = { [K in T]: { type: K; id: IDMAP[K]; } }[T]; const testInferrence = (arg: number) => {}; // 此时MyInterface<MyEnum, EnumTypeMap>是联合类型,支持类型收缩 const testMethod = (action: MyInterface<MyEnum, EnumTypeMap>) => { if (action.type === MyEnum.ONLY_NUMBER) { testInferrence(action.id); // 类型推断正常 } };
可辨识联合类型要求每个子类型都有一个共同的辨识符属性(这里就是type),且辨识符的取值在每个子类型中唯一。TypeScript会利用这个唯一辨识符,在类型守卫(比如if判断)时自动收缩对象类型,从而正确推断出其他属性的类型。
而你最初的泛型接口只是单一类型,id是所有可能类型的联合,TypeScript无法将单一类型的属性和辨识符建立关联,因此无法自动推断。
内容的提问来源于stack exchange,提问作者Yftach

