如何在TypeScript中提取子类继承的父类精确泛型类型?
解决TypeScript中从子类提取父类精确泛型类型的问题
问题描述
在TypeScript中,当子类继承使用具体泛型参数的父类时,尝试通过类型工具提取父类类型时,结果总是泛化为父类泛型参数的约束类型,而非子类继承时传入的具体类型。例如:
class Parent<A extends number, B extends string> { } class Child<A extends 4, B extends 3, C extends "a", D extends 1> extends Parent<D, C> { } type ToParent<T> = T extends Parent<infer A, infer B> ? Parent<A, B> : never; type Test = ToParent<Child<4, 3, "a", 1>>; // 实际得到 Parent<number, string>,期望得到 Parent<1, "a">
需求:
- 类型工具适用于任何继承
Parent的类 - 提取父类类型时保留子类继承时使用的精确泛型参数
- 不将类型放宽到父类泛型的约束类型
问题根源
原始的Parent类是空的(没有任何依赖泛型参数的成员),TypeScript的结构类型系统会将所有Parent<X,Y>实例视为结构相同的类型,因此条件类型中的infer会优先推断为泛型参数的约束类型(number和string),而非具体的传入值。
解决方案
要让TypeScript区分不同泛型参数的Parent实例,需要让它们具有不同的结构。以下两种方案可以实现需求:
方案1:添加依赖泛型参数的业务成员
在Parent类中添加使用泛型参数的属性或方法,让不同泛型参数的实例结构产生差异:
class Parent<A extends number, B extends string> { // 添加依赖泛型参数的属性,让TypeScript识别结构差异 parentData!: { valueA: A; valueB: B }; } class Child<A extends 4, B extends 3, C extends "a", D extends 1> extends Parent<D, C> { } type ToParent<T> = T extends Parent<infer A, infer B> ? Parent<A, B> : never; type Test = ToParent<Child<4, 3, "a", 1>>; // Test 解析为 Parent<1, "a">
方案2:使用名义类型标记(无业务侵入)
如果不想添加业务相关成员,可以添加唯一的标记属性和泛型参数占位属性,仅用于区分类型结构:
class Parent<A extends number, B extends string> { // 名义类型标记,确保Parent类型唯一性 readonly __parentBrand: unique symbol = Symbol(); // 泛型参数占位,让不同泛型实例结构不同 readonly __genericA!: A; readonly __genericB!: B; } class Child<A extends 4, B extends 3, C extends "a", D extends 1> extends Parent<D, C> { } type ToParent<T> = T extends Parent<infer A, infer B> ? Parent<A, B> : never; type Test = ToParent<Child<4, 3, "a", 1>>; // Test 解析为 Parent<1, "a">
说明
两种方案的核心都是让不同泛型参数的Parent实例具有不同的结构,从而让TypeScript的条件类型能够准确推断出子类继承时使用的具体泛型参数,而非泛化到约束类型。
内容的提问来源于stack exchange,提问作者Seph Reed
相关产品推荐
相关产品推荐

