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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 10:05:22