在 TypeScript 中实现 mixins 而不使用任何类型

问题描述

我正在纠结这个创建 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

表示编译器将 NewableFunctionSyntaxNewableMethodSyntax 视为本质上可以互换。)


通过将 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


Playground link to code