如何在TypeScript中将默认构造函数指定为参数

问题描述 投票:0回答:2

在StackOverflowee的帮助下,我已经确定了如何将构造函数指定为类的参数:

type GenericConstructor<T> = { new(): T; }

class MyClass<T> {
  subclass: T;
  constructor(
    SubClass: GenericConstructor<T>
  ) {
    this.subclass = new SubClass();
  }
}

class MySubClass1 { a = "" }

class MySubClass2 { b = "" }

const withSubClass1 = new MyClass(MySubClass1);
const withSubClass2 = new MyClass(MySubClass2);

[如果可能,我现在希望添加默认的SubClass,以便如果对默认功能满意的话,MyClass的用户就不必强制指定子类。

这是我希望能正常工作的代码(这不起作用!):

type GenericConstructor<T> = { new(): T; }

class DefaultSubClass { c = "" }

class MyClass<T> {
  subclass: T;
  constructor(
    SubClass: GenericConstructor<T> = DefaultSubClass // <== error!!
  ) {
    this.subclass = new SubClass();
  }
}

// …snip

TypeScript给我的错误是:

Type 'typeof DefaultSubClass' is not assignable to type 'GenericConstructor<T>'.
  Type 'DefaultSubClass' is not assignable to type 'T'.
    'DefaultSubClass' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'.

感谢您一如既往的帮助。

typescript
2个回答
1
投票

这不能是类型安全的,因为允许您指定显式类型参数。想象一下是否有人像这样调用您的构造函数:

let obj1: MyClass<MySubClass1> = new MyClass();
let obj2: MyClass<MySubClass2> = new MyClass();

这两个都不是类型错误,因为类型参数T可以在第一行中推断为MySubClass1,在第二行中推断为MySubClass2。在两种情况下,创建的对象实际上都不会满足类型注释,但更糟的是:即使您可以使它对其中一个起作用,也不能使其对两个都起作用,因为删除了尖括号中的类型参数在编译时,因此无法在运行时知道要使用哪个构造函数。

提供默认值的安全方法是使用非通用的静态工厂方法。这样,调用者可以使用MyClass.create()创建具有默认子类的类,但他们也不能要求T为其他子类。

class MyClass<T> {
  static create(): MyClass<DefaultSubClass> {
    return new MyClass(DefaultSubClass);
  }

  subclass: T;
  constructor(
    SubClass: GenericConstructor<T>
  ) {
    this.subclass = new SubClass();
  }
}

0
投票

假设这确实是您想要的行为,我可以想象有两种方法可以做到,但都不完美。


首先是将generic parameter default用于T

class DefaultSubClass { c = "hey" }
class OtherSubClass { d = 123 };

class MyClass<T = DefaultSubClass> {
  subclass: T;
  constructor(
    SubClass?: new () => T
  );
  constructor(SubClass?: new () => any) {
    if (!SubClass) SubClass = DefaultSubClass;
    this.subclass = new SubClass();
  }
}

console.log(new MyClass().subclass.c.toUpperCase()); // HEY
console.log(new MyClass(OtherSubClass).subclass.d.toFixed(2)) // 123.00

此行为依您的喜好,尽管请注意我必须overload构造函数,以便编译器不会(正确地)抱怨不确定DefaultClass是否可分配给T

这可能是最简单的解决方案。

这里的缺点是有人可以调用零参数构造函数并手动指定错误的T

console.log(new MyClass<OtherSubClass>().subclass.d.toFixed(2)); // oops

如果要防止这种情况,另一种方法是弱地键入类构造函数的值,然后将其分配给具有更强类型的值;这将需要类型声明,以避免编译器错误。几乎就像是用纯JavaScript编写构造函数,然后将TypeScript用作库来declare为它提供强类型一样:

class _MyClass {
  subclass: any;
  constructor(ctor: new () => any = DefaultSubClass) {
    this.subclass = new ctor();
  }
}

这是您实际的构造函数值。这是我们希望它具有的类型:

interface MyClass<T> {
  subclass: T;
}
interface MyClassCtor {
  new(): MyClass<DefaultSubClass>;
  new <T>(ctor: new () => T): MyClass<T>;
}

您可以看到MyClassCtor如何具有两个构造函数签名;零参数版本only创建一个MyClass<DefaultSubClass>,而一参数版本为相应的MyClass<T>创建一个T。然后,我们将构造函数值分配给构造函数类型的变量:

const MyClass = _MyClass as MyClassCtor;

然后,下面的行为仍会按照您的要求进行

console.log(new MyClass().subclass.c.toUpperCase()); // HEY
console.log(new MyClass(OtherSubClass).subclass.d.toFixed(2)) // 123.00

现在禁止这样做:

new MyClass<OtherSubClass>(); // error! wrong number of arguments

这太糟糕了,这要求将MyClass重命名,然后断言一个类型的复杂性。您不能直接执行此操作的原因是restriction on having a type parameter on constructor declarations。因此,虽然可以写入MyClassCtor的类型,但它不是直接可实现的。哦,很好。


好的,希望其中之一能为您提供帮助。祝你好运!

Playground link to code

© www.soinside.com 2019 - 2024. All rights reserved.