TypeScript中推断构造函数类型(InstanceType逆操作)及复杂类型静态属性兼容性校验问题
我完全懂你的困扰!你想在类型工具里,从实例类型反向拿到带静态成员的构造函数类型(相当于InstanceType的逆操作),这样才能正确校验类的静态属性兼容性,对吧?咱们一步步拆解问题,找到解决办法。
首先,得明确问题出在哪:你定义的Parent里,item的类型是Test——这是实例类型,而你的IsCompatible需要的是typeof Test(也就是带MustHave静态属性的构造函数类型)。直接把实例类型传给IsCompatible,肯定匹配不上,所以item被判定为never,这就是为什么ExtractCompatibleFields<Parent>返回的结果不符合预期。
你之前试过的typeof、T['constructor']都不管用,这很正常:
typeof在类型层面只能拿值的类型,没法从已有的实例类型反向推导构造函数类型;T['constructor']拿到的只是构造函数的签名,不带任何静态属性信息,自然没法用来校验MustHave。
TypeScript本身没有内置从实例类型直接获取带静态属性的构造函数类型的工具,但我们可以通过两种方案来绕开这个限制:
方案一:用标记类关联实例与构造函数类型(推荐,类型安全)
这个思路是给类加个「标记」,让实例类型和构造函数类型建立关联,这样就能从实例类型里反向提取构造函数类型了。
第一步,先定义一个泛型标记类,用来承载构造函数类型的信息:
// 泛型标记类,专门用来关联实例类型和构造函数类型 class ConstructorMarker<ConstructorType> { // 静态占位属性,用来存储构造函数类型的类型信息 static readonly __constructorType?: never; }
第二步,修改你的Test类,让它继承这个标记类,并且把构造函数类型(typeof Test)传进去:
class Test extends ConstructorMarker<typeof Test> { public constructor() { super(); } static readonly MustHave: boolean = true; property1: boolean; property2: string; }
第三步,写一个类型工具,从实例类型里提取构造函数类型:
// 从实例类型反向提取对应的构造函数类型 type GetConstructor<InstanceType> = InstanceType extends ConstructorMarker<infer CT> ? CT : never;
第四步,修改IsCompatible和HasStaticMustHave,让它们能处理实例类型:
// 专门校验构造函数类型是否有目标静态属性 type HasStaticMustHave<ConstructorType> = ConstructorType extends { MustHave: boolean } ? true : false; // 兼容实例类型和构造函数类型的兼容性校验 type IsCompatible<T> = // 如果传入的是构造函数类型,直接校验 T extends { MustHave: boolean } ? true : // 如果传入的是实例类型,先提取构造函数类型再校验 HasStaticMustHave<GetConstructor<T>>;
现在再试试ExtractCompatibleFields<Parent>,就会得到你想要的{ item: Test; other_item: never; }了!
这个方案的好处是完全类型安全,实例和构造函数类型自动关联,不用手动维护额外的映射;唯一的小缺点是需要修改现有类的定义,让它们继承标记类。
方案二:手动维护实例-构造函数类型映射(无需修改类)
如果你不想修改现有类的定义,也可以手动维护一个映射表,把实例类型和对应的构造函数类型关联起来:
第一步,定义映射表:
// 手动维护实例类型到构造函数类型的映射 type InstanceToConstructorMap = { Test: typeof Test; // 以后新增需要校验的类,都要在这里加一条映射 };
第二步,修改IsCompatible:
type HasStaticMustHave<ConstructorType> = ConstructorType extends { MustHave: boolean } ? true : false; type IsCompatible<T> = // 检查实例类型是否在映射表里,再校验对应的构造函数类型 T extends keyof InstanceToConstructorMap ? HasStaticMustHave<InstanceToConstructorMap[T]> : false;
这样也能得到正确的结果,但这个方案需要你手动维护映射表,新增类的时候别忘了更新,不然会校验失败,适合不想修改类定义的场景。
总结一下:TypeScript本身没有内置实例类型到构造函数类型的反向推导工具,但我们可以通过标记类关联或者手动映射的方式,把实例类型和构造函数类型绑定起来,从而实现静态属性的兼容性校验。根据你的实际场景选一个合适的方案就行!
内容来源于stack exchange

