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

TypeScript中定义子类映射并运行时创建子类的优雅实现

解决TypeScript抽象类子类映射的实例化编译错误问题

这是个很典型的TypeScript类型系统问题,咱们先搞清楚为什么会报错,再给你几个优雅的解决方案。

问题根源

你用typeof A作为映射对象的类型时,typeof A代表的是抽象类A的构造函数类型。TypeScript的类型系统会严格禁止实例化抽象类——哪怕你实际存入的都是A的非抽象子类,类型系统没法确定这一点,所以会抛出“Cannot create an instance of abstract class”的编译错误。

而用typeof B | typeof C虽然可行,但子类一多就会变得无比冗长,完全不符合可维护性要求。

优雅解决方案:定义非抽象构造函数类型

核心思路是定义一个专门的类型,用来描述「可以实例化出A类实例的非抽象构造函数」,替代typeof A。

方案1:固定构造参数的构造函数类型

如果A的构造函数参数是固定的,直接明确定义构造函数类型:

abstract class A { 
  public name: string; 
  constructor(name: string) { this.name = name; } 
  abstract getName(): string; 
}
class B extends A { getName(): string { return "B: " + this.name; } }
class C extends A { getName(): string { return "C: " + this.name; } }

// 定义能创建A实例的非抽象构造函数类型
type AConstructor = new (name: string) => A;

// 映射对象使用这个类型
const classes: { [type: string]: AConstructor } = { b: B, c: C };

function test(type: string, name: string) {
  if (!classes.hasOwnProperty(type)) { throw new Error(`Invalid type: ${type}`); }
  const _class = classes[type];
  return new _class(name); // 编译完全正常
}

这个类型AConstructor明确告诉TypeScript:映射里的每个值都是可以被new、且返回A实例的构造函数,B和C的构造函数完全符合这个要求,所以不会再报错。后续新增A的子类时,只需要把它加到classes对象里,类型定义完全不需要修改。

方案2:通用构造函数类型(适配构造参数变化)

如果A的构造函数参数可能会变动,或者想让类型更通用,可以用TypeScript内置的ConstructorParameters和InstanceType工具类型来自动推导:

// 自动提取A的构造参数和实例类型
type AConstructor = new (...args: ConstructorParameters<typeof A>) => InstanceType<typeof A>;

const classes: { [type: string]: AConstructor } = { b: B, c: C };

// 后续test函数写法和之前一致
function test(type: string, name: string) {
  if (!classes.hasOwnProperty(type)) { throw new Error(`Invalid type: ${type}`); }
  const _class = classes[type];
  return new _class(name);
}

这种方式的好处是,如果后续修改A的构造函数参数,AConstructor会自动同步更新,不需要手动调整类型定义。

进阶:让返回类型更精确

如果你希望test函数返回的实例类型是具体的子类(比如调用test('b', 'xxx')返回B类型,而不是A类型),可以用类型字面量约束type参数:

// 定义允许的类型字面量
type ValidClassType = 'b' | 'c';

// 使用Record来约束映射对象的键
const classes: Record<ValidClassType, AConstructor> = { b: B, c: C };

// 泛型函数推导返回类型
function test<T extends ValidClassType>(type: T, name: string): InstanceType<typeof classes[T]> {
  const _class = classes[type];
  return new _class(name) as InstanceType<typeof classes[T]>;
}

// 调用时会自动推导类型
const bInstance = test('b', 'test'); // bInstance的类型是B
const cInstance = test('c', 'test'); // cInstance的类型是C

这种方式兼顾了类型安全和返回类型的精确性,适合对类型要求较高的场景。

总结

不管用哪种方案,核心都是用一个明确的非抽象构造函数类型替代typeof A,既解决了编译错误,又避免了冗长的联合类型定义,后续扩展子类也非常方便。

内容的提问来源于stack exchange,提问作者Rocco

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 09:33:50