从概念上讲,您想说class MyClass<T extends object> extends T 或class MyClass<T extends object> implements T,但TypeScript 不允许class 实例或interface 具有动态键。 class 或 interface 声明的所有键必须是静态已知的。因此,如果您想要这种行为,您需要做的不是class 声明。
相反,您可以描述所需的MyClass<T> 实例的类型以及MyClass 类构造函数的类型,然后使用type assertion 告诉编译器您的实际构造函数对象属于该类型.
假设您想要的MyClass<T> 类实例具有T 的所有属性,外加一个名为someMethodIGuess() 的方法。那么你的MyClass<T> 类型可以这样定义:
type MyClass<T extends object> = T & {
someMethodIGuess(): void;
}
您希望您的MyClass 类构造函数有一个construct signature,它为某些generic T 对象类型接受T 类型的参数,并生成MyClass<T> 的实例。可以这样定义:
type MyClassConstructor = {
new <T extends object>(arg: T): MyClass<T>
}
要实现这个类,我们可以使用 [Object.assign()] 将构造函数参数属性以及我们需要的任何方法或其他东西复制到实例中。
const MyClass = class MyClass {
constructor(arg: any) {
Object.assign(this, arg);
}
someMethodIGuess() {
}
}
但是当然,如果我们这样离开它,编译器将不会将MyClass 视为MyClassConstructor(毕竟它不能有动态键,我什至没有尝试告诉它@ 987654348@ 这里)。我们需要一个类型断言来告诉它,像这样:
const MyClass = class MyClass {
constructor(arg: any) {
Object.assign(this, arg);
}
someMethodIGuess() {
}
} as MyClassConstructor;
编译没有错误。再次注意,编译器无法理解 MyClass 实现符合 MyClassConstructor 类型。通过使用类型断言,我已经将确保正确实现它的负担从编译器(它不能这样做)转移到我(或者如果你使用这段代码的话)。所以我们应该非常小心地检查我们做了什么以及我们没有写错东西(例如,Object.assign(arg, this); 而不是Object.assign(this, arg); 会是一个问题)。
让我们测试一下:
const myInstance = new MyClass(({
someField: 'foo'
}))
console.log(myInstance.someField.toUpperCase()); // FOO
myInstance.someMethodIGuess(); // okay
看起来不错!编译器期望myInstance 具有string 类型的someField 属性,以及someMethodIGuess() 方法。
就是这样。请注意,使用 MyClass 的方式与使用另一个类构造函数相同。例如,编译器永远不会对具有动态键的 class 声明感到满意,因此如果您尝试使用 class 声明创建 MyClass 的通用子类,您将收到错误:
class SubClass<T extends object> extends MyClass<T> { // error
anotherMethod() { }
}
如果你需要这种东西,你会发现自己必须使用与以前相同的技巧来处理子类:
type SubClass<T extends object> = MyClass<T> & { anotherMethod(): void };
type SubClassConstructor = { new <T extends object>(arg: T): SubClass<T> };
const SubClass = class SubClass extends MyClass<any> {
anotherMethod() { }
} as SubClassConstructor;
Playground link to code