在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 '{}'.
感谢您一如既往的帮助。
这不能是类型安全的,因为允许您指定显式类型参数。想象一下是否有人像这样调用您的构造函数:
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();
}
}
假设这确实是您想要的行为,我可以想象有两种方法可以做到,但都不完美。
首先是将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
的类型,但它不是直接可实现的。哦,很好。
好的,希望其中之一能为您提供帮助。祝你好运!