首页 > 解决方案 > 在 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,
          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

标签: typescriptmixins

解决方案


问题是这Function是一个非常广泛的类型,包括任何函数,包括那些不能通过new. 因此编译器抱怨它Function是不可构造的。因此,解决方案是使用已知具有构造签名的类型。在 TypeScript 中,这样的签名通过将关键字添加new到函数签名来表示:

type NewableFunctionSyntax = new () => object;
type NewableMethodSyntax = { new(): object };

这些类型都表示一个不接受任何参数的构造函数,并生成一个 assignable 类型的实例object。请注意,虽然这些语法不同,但它们本质上是相同的。(要看到这一点,请注意编译器允许您声明 avar多次,但如果您使用不同的类型对其进行注释,则会报错。以下编译没有错误的事实,

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 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()会阻止你调用它会自动为您服务。(有关详细信息,请参阅microsoft/TypeScript#2310class.ts


Playground 代码链接


推荐阅读