TypeScript中Mixin传入类的原型被识别为any的问题及解决
问题
我想把传入Mixin函数的类,作为Mixin返回类的方法参数类型。比如希望下面示例里method1的参数类型是BaseClass:
class BaseClass { a = 123; } class SomeClass extends Mixin(BaseClass) { constructor() { super() // 这里参数类型应该是'BaseClass' this.method1({ a: "aaa" }) } }
我尝试了下面的写法,但遇到了问题:
type Constructor<T = {}> = new (...args: any[]) => T; class BaseClass { a = 123; } export function Mixin<T extends Constructor>(ExtendedClass: T) { return class extends ExtendedClass { method1(baseClass: typeof ExtendedClass.prototype) { } } } class SomeClass extends Mixin(BaseClass) { constructor() { super() // 这里baseClass被识别为'any',不会触发编译错误 this.method1({ a: "aaa" }) // 这里符合预期,触发编译错误 this.method2({ a: "aaa" }) } method2(baseClass: typeof BaseClass.prototype) { } }
问题在于:TypeScript把method1里的typeof ExtendedClass.prototype识别成了any,而不是BaseClass,但method2直接用typeof BaseClass.prototype却能正常识别类型。我想让method1和method2表现一致,请问原因是什么?怎么解决?
原因分析
你定义的Constructor<T>类型默认值是{},且泛型T extends Constructor没有绑定具体的实例类型信息。当你使用typeof ExtendedClass.prototype时,TypeScript无法从T extends Constructor中推导ExtendedClass的具体实例类型,只能退化为any。
而method2里直接引用typeof BaseClass.prototype,是明确指向具体类的实例类型,TypeScript能直接识别出这是BaseClass的实例类型,因此可以正常做类型校验。
解决方案
修改Mixin函数的泛型定义,让它绑定到类的实例类型上,而非仅绑定构造函数类型。具体来说,把泛型参数改为代表实例类型,再通过这个类型推导对应的构造函数类型:
// 定义构造函数类型,关联实例类型T type Constructor<T = {}> = new (...args: any[]) => T; class BaseClass { a = 123; } // 泛型T代表实例类型,Constructor<T>是对应的构造函数类型 export function Mixin<T>(ExtendedClass: Constructor<T>) { return class extends ExtendedClass { // 直接用T作为参数类型,就是传入类的实例类型 method1(baseClass: T) { } } } class SomeClass extends Mixin(BaseClass) { constructor() { super() // 现在这里会触发编译错误,符合预期 this.method1({ a: "aaa" }) this.method2({ a: "aaa" }) } method2(baseClass: typeof BaseClass.prototype) { } }
修改后,Mixin函数的泛型T会自动推导为BaseClass的实例类型,method1的参数类型会被正确识别为BaseClass,和method2表现完全一致,传入不符合类型的值时会触发编译错误。
内容的提问来源于stack exchange,提问作者unitario

