问题描述
我正在纠结这个创建 mixin 的 TypeScript 代码:
function applyMixins(derivedCtor: Function,constructors: Function[]) {
//copies methods
constructors.forEach((baseCtor) => {
Object.getownPropertyNames(baseCtor.prototype).forEach((name) => {
Object.defineProperty(
derivedCtor.prototype,name,Object.getownPropertyDescriptor(baseCtor.prototype,name) ||
Object.create(null)
);
});
});
//copies properties
constructors.forEach((baseCtor) => {
let empty = new baseCtor();
Object.keys(empty).forEach((name) => {
Object.defineProperty(
derivedCtor.prototype,Object.getownPropertyDescriptor(empty,name) ||
Object.create(null)
);
});
});
}
它不编译,抱怨这一行:let empty = new baseCtor();
:TS2351: This expression is not constructable. Type 'Function' has no construct signatures.
。我知道这可以通过将第一行中的 Function
类型引用与 any
交换来解决,但我试图在没有 any
的情况下过我的生活,因为它通常很草率告诉 TypeScript 闭嘴的方法。
有没有办法在不使用 any
的情况下实现此代码?
解决方法
问题是 Function
是一个非常广泛的类型,包括任何函数,包括不能通过 new
调用的函数。因此编译器抱怨 Function
是不可构造的。因此,解决方案是使用已知具有construct signature的类型。在 TypeScript 中,这样的签名是通过在函数签名前添加关键字 new
来表示的:
type NewableFunctionSyntax = new () => object;
type NewableMethodSyntax = { new(): object };
这些类型都表示一个不接受任何参数的构造函数,并产生一个可赋值类型 object
的实例。请注意,虽然这些语法不同,但它们本质上是相同的。 (要看到这一点,请注意,编译器允许您多次声明 var
,但如果您使用不同类型对其进行注释,则会报错。以下编译没有错误的事实,
var someCtor: NewableFunctionSyntax;
var someCtor: NewableMethodSyntax; // no error
表示编译器将 NewableFunctionSyntax
和 NewableMethodSyntax
视为本质上可以互换。)
通过将 Function
更改为其中之一,您的代码现在可以无错误地编译:
function applyMixins(derivedCtor: { new(): object },constructors: { new(): object }[]) {
//Copies methods
constructors.forEach((baseCtor) => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
Object.defineProperty(
derivedCtor.prototype,name,Object.getOwnPropertyDescriptor(baseCtor.prototype,name) ||
Object.create(null)
);
});
});
//Copies properties
constructors.forEach((baseCtor) => {
let empty = new baseCtor();
Object.keys(empty).forEach((name) => {
Object.defineProperty(
derivedCtor.prototype,Object.getOwnPropertyDescriptor(empty,name) ||
Object.create(null)
);
});
});
}
让我们测试调用 applyMixins()
以确保我们了解 {new(): object}
匹配和不匹配的内容:
class Works {
x = 1;
constructor() { }
}
applyMixins(Works,[]); // okay
Works
很好,因为它是一个不带参数的类构造函数。
class CtorRequiresArg {
y: string;
constructor(y: string) { this.y = y; }
}
applyMixins(CtorRequiresArg,[]); // error!
// -------> ~~~~~~~~~~~~~~~
// Type 'new (y: string) => CtorRequiresArg'
// is not assignable to type 'new () => object'
CtorRequiresArg
失败,因为在构造它时必须传递 string
参数,例如 new CtorRequiresArg("hello")
... 但是 applyMixins()
只接受可以在没有任何参数的情况下调用的构造函数参数。
最后:
function NotACtor() { }
applyMixins(NotACtor,[]); // error!
// -------> ~~~~~~~~
// Type '() => void' provides no match
// for the signature 'new (): object'
NotACtor
失败,因为它不被认为是可构造的。这可能令人惊讶,因为在运行时没有什么可以阻止您调用 new NotACtor()
,但是编译器认为,如果您想要一个类构造函数,您将在 class
文件中使用 .ts
表示法。 .. 即使针对 ES5 运行时,因为 TypeScript 会自动为您降低级别。 (有关详细信息,请参阅 microsoft/TypeScript#2310)