【问题标题】:TypeScript: Decorating a derived class with typed decorator functionTypeScript:使用类型化装饰器函数装饰派生类
【发布时间】:2019-07-23 20:39:15
【问题描述】:

我正在尝试使用 TypeScript 为 node.js 和 MongoDB 构建一个类似于“实体框架”的 ORM 库。

使用这个库,消费者将能够定义一个模型类(例如Person)来扩展Base 类,并且类装饰器将向 Person 类添加额外的数据(例如,@ 987654324@ 数组,将包含模型的所有实例,collection_name 将是模型的 MongoDB 集合名称等)。

TypeScript playground 中的代码。


所以我的第一步是创建一个Base 类:

class Base<T>
{
    static collection_name: string
    static instances: Base<any>[]
    _id: string
}

所以用户可以像这样定义他的模型:

@decorator<Person>({collection_name: 'people'})
class Person extends Base<Person>
{
    @field name
    @field address
    ...
}

然后我创建了一个装饰器类来设置 Person 类的 collection_nameinstances 属性:

function decorator<T>(config: { collection_name: string }) {
    return function <T extends Base<T>>(Class: IClass<T>) {
        Class.collection_name = config.collection_name;
        Class.instances = [];
        return Class
    }
}

装饰器函数接收用户生成的Class,我正在尝试创建一个接口来描述此类的类型。我叫它IClass

interface IClass<T>
{
    new(): Base<T>

    instances: Base<T>[];
    collection_name: string
}
  • new 是构造函数(返回 Base 实例)
  • instancescollection_nameBase&lt;T&gt; 的静态属性,在这里是非静态的(我不确定,对吗?)

但是,当尝试定义用户模型时,我收到以下错误:

@decorator<Person>({collection_name: 'people'})   // <==== ERROR HERE ===
class Person extends Base<Person>
{
}

错误:(23, 2) TS2345: 'typeof Person' 类型的参数不是 可分配给“IClass>”类型的参数。

“typeof Person”类型中缺少属性“instances”,但 类型“typeof Person”缺少类型中的以下属性 在类型“IClass>”中是必需的。

在检查typeof Person 的类型时,打字稿编译器似乎忽略了从Base 继承的静态成员。

如何定义装饰器函数的Class属性的类型?

【问题讨论】:

  • type PersonConstructor = typeof Person; 然后使用@decorator&lt;PersonConstructor&gt;
  • @PatrickRoberts 感谢您对编辑的评论。但是......不,这没有成功,仍然是同样的错误。另外,Person 类是一个用户定义的类,所以我试图让语法尽可能简单、简短和可用
  • 很公平,没有尝试过,只是看起来这可能是查看您最初错误的技巧。让我看看我是否真的可以让你的例子工作,我会告诉你的。
  • 您的直接问题是 decorator() 返回一个函数,该函数只接受 已经 类型为 IClass&lt;T&gt;... 但您正试图采用 任何构造函数并返回IClass&lt;T&gt;,对吗?
  • 我找到了一个可行的解决方案,jcalz 是对的。我现在正在发布答案。您需要将您的界面分成两部分才能完成这项工作。

标签: node.js typescript typescript-generics typescript-decorator typescript-class


【解决方案1】:

问题as jcalz points out 是您的装饰器正在接受Class 的类型已经 具有静态属性instancescollection_name。您需要使用两种不同的接口,一种是使用new(): T 签名简单地构造T 实例的类型,另一种是扩展此接口以包含装饰器将添加的静态属性的类型。

class Base<T> {
    static _id = 0;
    private _id: number;

    constructor () {
        this._id = Base._id++;
    }
}

interface BaseConstructor<T extends Base<T>> {
    _id: number;
    new(): T;
}

interface DecoratedBaseConstructor<T extends Base<T>> extends BaseConstructor<T> {
    instances: T[];
    collection_name: string;
}

function decorator<T extends Base<T>>(config: { collection_name: string }) {
    return (baseConstructor: BaseConstructor<T>): DecoratedBaseConstructor<T> => {
        const decoratedBaseConstructor = baseConstructor as Partial<DecoratedBaseConstructor<T>>;
        decoratedBaseConstructor.collection_name = config.collection_name;
        decoratedBaseConstructor.instances = [];
        return decoratedBaseConstructor as DecoratedBaseConstructor<T>;
    };
}

@decorator<Person>({collection_name: 'people'})
class Person extends Base<Person> {
    name: string;

    constructor () {
        super();
        this.name = 'foo';
    }
}

使用这种方法,Base 的所有static 成员都必须是公开的。在装饰器中初始化的Base 的任何static 成员都应该放在DecoratedBaseConstructor 中,而在装饰器中未初始化的任何剩余static 成员应该放在BaseConstructor 中。

我假设您在实际代码中以某种方式在 Base 类中使用泛型类型 T,但如果您不这样做,您应该从 Base 类中删除泛型类型,其他所有内容仍将工作相同。

查看上面的 sn-p in this playground

【讨论】:

  • 谢谢,这太棒了,成功了!我仍然需要提高我的 TS 技能:)
  • @PatricRoberts 我担心这个解决方案有一个小故障......当用户扩展 Person 类时,DecoratedBaseConstructor 不再可分配给它。要点:gist.github.com/rotemx/b6509599c6f3c0d4d4098dd0a3a4be93
  • @PatricRoberts Base 类中的行为实现怎么样?这是实际代码所在的位置,例如 save()、update() 等。
  • 或者我应该在装饰器中实现所有东西?
  • @rotemx 你介意告诉我为什么这个答案对你不起作用再次吗?
猜你喜欢
  • 2022-08-15
  • 2015-10-23
  • 2011-10-04
  • 2019-05-02
  • 2020-06-20
  • 2011-09-17
  • 2014-01-14
  • 2011-06-06
  • 2017-03-28
相关资源
最近更新 更多