为联合类型中的两个构造器应用Mixin模式
解决TypeScript联合构造器类型的Mixin类型推断问题
问题原因
当你把Mixin的泛型约束设为Class1Constructor | Class2Constructor联合类型时,TypeScript无法准确识别联合成员的共有属性和方法——它只会解析所有联合成员从抽象父类继承的公共特征,子类各自定义的_lastName和doSomethingWithValue会被视为联合类型的“差异部分”,导致类型检查失败,同时super的类型会退化为any。
解决方案
核心思路是提取子类的公共特征到统一接口,让Mixin基于这个公共接口的构造器做约束,而非直接使用联合类型,以此让TypeScript正确推断属性和方法的类型。
步骤1:定义公共接口
先创建包含两个子类共有属性、方法的接口:
interface HasLastNameAndDoSomething { _lastName: string; doSomethingWithValue(): number; }
步骤2:调整构造器类型约束
用上述接口定义构造器类型,替换原来的联合类型约束:
type GenericConstructor<T> = new (...args: any[]) => T; // 基于公共接口的构造器类型 type BaseConstructor = GenericConstructor<HasLastNameAndDoSomething>;
步骤3:修改Mixin函数
用新的BaseConstructor作为泛型约束,此时this和super的类型会被正确识别:
export function ExtendedClassOneOrTheOther<TBase extends BaseConstructor>(Base: TBase) { return class ExtendedClass extends Base { public lastName() { return this._lastName; // 类型检查通过 } public doSomethingWithValue() { const newValue = super.doSomethingWithValue(); // super类型正确推断 return newValue * 100; } }; }
完整可运行代码
abstract class AsbtractClass<N extends "Matt" | "Tom", V extends 1 | 2> { public abstract attributeName(): N; public abstract attributeValue(): V; public name: N; public value: V; constructor() { this.name = this.attributeName(); this.value = this.attributeValue(); } } class Class1 extends AsbtractClass<"Matt", 1> { public _lastName = "Murdock"; public attributeName() { return "Matt" as const; } public attributeValue() { return 1 as const; } public doSomethingWithValue() { return Math.random() + this.value; } } class Class2 extends AsbtractClass<"Tom", 2> { public _lastName = "Smith"; public attributeName() { return "Tom" as const; } public attributeValue() { return 2 as const; } public doSomethingWithValue() { return Math.random() + this.value + 2; } } // 公共特征接口 interface HasLastNameAndDoSomething { _lastName: string; doSomethingWithValue(): number; } type GenericConstructor<T> = new (...args: any[]) => T; type BaseConstructor = GenericConstructor<HasLastNameAndDoSomething>; export function ExtendedClassOneOrTheOther<TBase extends BaseConstructor>(Base: TBase) { return class ExtendedClass extends Base { public lastName() { return this._lastName; } public doSomethingWithValue() { const newValue = super.doSomethingWithValue(); return newValue * 100; } }; } // 使用示例 const ExtendedClass1 = ExtendedClassOneOrTheOther(Class1); const instance1 = new ExtendedClass1(); console.log(instance1.lastName()); // 输出 "Murdock" console.log(instance1.doSomethingWithValue()); // 正确执行计算 const ExtendedClass2 = ExtendedClassOneOrTheOther(Class2); const instance2 = new ExtendedClass2(); console.log(instance2.lastName()); // 输出 "Smith" console.log(instance2.doSomethingWithValue()); // 正确执行计算
额外说明
如果后续新增子类,只要子类符合HasLastNameAndDoSomething接口的定义,就能直接复用这个Mixin。这种方式既保证了类型安全,又保留了Mixin的通用性。
内容的提问来源于stack exchange,提问作者Rodrigo J Moura
相关产品推荐
相关产品推荐

