在不使用任何类型的情况下在 TypeScript 中实现混合

Implement mixins in TypeScript without using any type

我正在纠结这个创建 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,
          name,
          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,
                name,
                Object.getOwnPropertyDescriptor(empty, name) ||
                Object.create(null)
            );
        });
    });
}

让我们测试调用 applyMixins() 以确保我们了解 {new(): object} 匹配和不匹配的内容:

class Works {
    x = 1;
    constructor() { }
}
applyMixins(Works, []); // okay

Works 很好,因为它是一个不带参数的 class 构造函数。

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 构造函数,你将在 [=35] 中使用 class 表示法=] 文件...即使是针对 ES5 运行时,因为 TypeScript 会自动为您降级。 (有关详细信息,请参阅 microsoft/TypeScript#2310


Playground link to code