You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为联合类型中的两个构造器应用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 17:45:29