TypeScript子类联合类型的类型推断异常问题求助
解决TypeScript泛型联合类型遍历的类型推断问题
问题场景
定义泛型基类Something<T>及其子类AnotherThing、YetAnotherThing,将子类组成联合类型SomethingUnion,再定义包含该联合类型的对象类型SomethingMap,以及提取泛型参数的映射类型DataOf。遍历SomethingMap类型的someMap条目时,调用thing.doSomething()的参数被推断为never类型,无法匹配someOtherMap[key](即DataOf<typeof someMap>类型的值)的类型。
原始代码示例
// Base class class Something <T> { constructor (x: T) { // ... } doSomething (value: T) { // ... } } // Subclasses class AnotherThing extends Something<string> {} class YetAnotherThing extends Something<number> {} // Union of subclasses type SomethingUnion = AnotherThing | YetAnotherThing; type SomethingMap = { [key: string]: SomethingUnion; } // A type that extracts `T` from `Something<T>`. type GetT<S extends Something<any>> = S extends Something<infer U> ? U : never // A mapped type that extracts the type parameter from every `Something` subclass. type DataOf<T extends SomethingMap> = { [K in keyof T]: GetT<T[K]>; } // 问题代码 // Note: someMap declared previously with type `SomethingMap` // Note: someOtherMap declared previously with type equivalent to `DataOf<typeof someMap>` const entries = Object.entries(someMap); // [string, SomethingUnion][] const mappedEntries = entries.map(([key, thing]) => { const valueToDoThingsWith = someOtherMap[key] // string | number // value has the type `SomethingUnion`, and the parameter `value` is `never`, which // does not work with `valueToDoThingsWith`, which holds the intended type "string | number". thing.doSomething(valueToDoThingsWith) // ... })
解决方案
问题根源在于:SomethingUnion是联合类型,调用doSomething时TypeScript会取两个子类方法参数的交集(string & number = never);同时Object.entries丢失了key与对应值类型的关联,导致类型推断失效。
方法:使用泛型函数保留类型关联
通过定义泛型函数,让TypeScript跟踪每个key对应的具体类型,而非抹平成宽泛的联合类型:
// Base class class Something<T> { constructor(x: T) {} doSomething(value: T) {} } // Subclasses class AnotherThing extends Something<string> {} class YetAnotherThing extends Something<number> {} // 定义具体的映射类型(替代原有的SomethingMap,保留key与类型的关联) type SpecificMap = { a: AnotherThing; b: YetAnotherThing; }; // 提取泛型参数的类型 type GetT<S extends Something<any>> = S extends Something<infer U> ? U : never; // 生成对应的数据映射类型 type DataOf<M extends Record<string, Something<any>>> = { [K in keyof M]: GetT<M[K]>; }; // 初始化具体实例 const someMap: SpecificMap = { a: new AnotherThing("test"), b: new YetAnotherThing(123), }; const someOtherMap: DataOf<SpecificMap> = { a: "hello", b: 456, }; // 泛型处理函数,保留key与类型的关联 function processMap<M extends Record<string, Something<any>>>( map: M, otherMap: DataOf<M> ) { // 将Object.keys断言为keyof M,保留具体key类型 (Object.keys(map) as Array<keyof M>).forEach((key) => { const thing = map[key]; const value = otherMap[key]; thing.doSomething(value); // 类型推断正确,参数类型与value完全匹配 }); } // 调用处理函数 processMap(someMap, someOtherMap);
原理说明
- 用具体的映射类型(如
SpecificMap)替代宽泛的SomethingMap,保留每个key对应的子类类型信息; - 泛型函数
processMap通过类型参数M,关联map和otherMap的key与对应类型,让TypeScript能准确推断每个thing.doSomething的参数类型; - 将
Object.keys(map)断言为Array<keyof M>,避免key类型被抹平为string,确保类型关联不丢失。
内容的提问来源于stack exchange,提问作者R2509
相关产品推荐
相关产品推荐

