为何TypeScript无法从子类类型推断基类泛型类型?
问题分析与解决
问题现象
在TypeScript 4.9.3中,以下代码里ExtractEvents<FooEmitter>的推断结果是EventsMap而非预期的FooEvents,导致ShouldBeTrue2返回false:
// @strict: true type EventsMap = Record<string, (...args: any[]) => any>; declare class TypedEmitter<L extends EventsMap> { on<U extends keyof L>(event: U, listener: L[U]): this; off<U extends keyof L>(event: U, listener: L[U]): this; emit<U extends keyof L>(event: U, ...args: Parameters<L[U]>): boolean; } type ExtractEvents<Candidate> = Candidate extends TypedEmitter<infer E> ? E : never; type FooEvents = { foo: (payload: string) => void; } declare class FooEmitter extends TypedEmitter<FooEvents> {} type ExtendsFooEvents<C> = C extends FooEvents ? true : false; // 结果为true,符合预期 type ShouldBeTrue1 = ExtendsFooEvents<ExtractEvents<TypedEmitter<FooEvents>>>; // 结果为false,不符合预期 type ShouldBeTrue2 = ExtendsFooEvents<ExtractEvents<FooEmitter>>;
原因解释
这是TypeScript泛型继承中的类型推断行为导致的:
- 当直接传入
TypedEmitter<FooEvents>时,TypeScript能明确识别这是泛型类的具体实例化,因此infer E会直接推断为FooEvents。 - 但当传入子类
FooEmitter时,TypeScript仅能识别它继承自TypedEmitter<L extends EventsMap>,此时条件类型会优先使用泛型参数的约束上限(即EventsMap)作为推断结果,而非父类实例化时传入的具体类型FooEvents。子类的实例类型不会自动保留父类的具体泛型参数信息。
解决方案
有两种简洁的修复方式:
方案1:在父类中添加类型推断用的幽灵属性
在TypedEmitter中添加一个仅用于类型推断的属性(无需实际实现),用来显式绑定泛型参数L:
declare class TypedEmitter<L extends EventsMap> { // 幽灵属性,仅用于类型系统推断 declare readonly __eventMap: L; on<U extends keyof L>(event: U, listener: L[U]): this; off<U extends keyof L>(event: U, listener: L[U]): this; emit<U extends keyof L>(event: U, ...args: Parameters<L[U]>): boolean; } // 修改ExtractEvents来读取这个属性 type ExtractEvents<Candidate> = Candidate extends { __eventMap: infer E } ? E : never;
方案2:调整条件类型的推断逻辑
通过嵌套条件类型,强制TypeScript深入推断具体的泛型参数:
type ExtractEvents<Candidate> = Candidate extends TypedEmitter<infer E> ? E extends EventsMap ? E : never : never;
两种方案都能让ExtractEvents<FooEmitter>正确推断为FooEvents,使得ShouldBeTrue2返回true。
内容的提问来源于stack exchange,提问作者Jules Sam. Randolph
相关产品推荐
相关产品推荐

