【问题标题】:Extending type when extending an ES6 class with a TypeScript decorator使用 TypeScript 装饰器扩展 ES6 类时扩展类型
【发布时间】:2019-07-20 09:40:53
【问题描述】:

我正在尝试用装饰器(a-la-angular 样式)装饰一个类,并为其添加方法和属性。

这是我的示例装饰类:

@decorator
class Person{

}

这是装饰器:

const decorator = (target)=>{
    return class New_Class extends target {
        myProp:string
    }
}

myProp 不是 Person 的已知属性:

person.myProp //Error - myProp does not exist on type Person

如何装饰 typescript 类并保留类型完成、类型安全等?

【问题讨论】:

    标签: typescript ecmascript-6 es6-class typescript-decorator typescript-class


    【解决方案1】:

    为了补充jcalz response,回到Decorator Pattern的定义,它不会改变其目标的接口/合约。这不仅仅是术语。 TypeScript 装饰器与 Java 注释和 .NET 属性有相似之处,这与不更改接口的事实相一致:它们只是添加元数据。

    类 mixin 是解决您问题的理想选择。但最好不要在名称中使用“装饰器”,以免混淆。

    【讨论】:

      【解决方案2】:

      我找到了一种实现多种遗产的解决方案(实际上是级联的),但值得一看。

      假设您有一个 Base 类,其中包含一些属性和方法:

      class Base {
          tableName = 'My table name';
          hello(name) {
           return `hello ${name}`;
          }
      }
      

      你想要一个类来扩展 Base 但你也定义了一些你想重用的属性。为此将执行以下功能:

      type Constructor<T = {}> = new (...args: any[]) => T;
      function UserFields<TBase extends Constructor>(Base: TBase) {
          return class extends Base {
              name: string;
              email: string;
          };
      }
      

      现在我们可以做一个扩展 Base 和扩展 UserFields 的类,打字稿语言服务将从这两个类中找到属性。它模拟了多重遗产,但它实际上是一个级联。

      class User extends UserFields(Base) { }
      const u = new User();
      u.tableName = 'users'; // ok
      u.name = 'John'; // ok
      

      通过这种方式,您可以将 UserFields 函数与任何其他类一起使用。 一个明显的例子是,如果您想在客户端将 User 对象公开为“干净”对象并具有可用字段,然后您有一个 UserDb 对象与数据库连接,并且任何其他服务器端方法都可以具有相同的字段也。我们只定义一次数据库字段!

      另一个好处是你可以链接 mixin mixin1(mixin2....(Base)) 以在同一个类中拥有尽可能多的属性。

      每个人都希望装饰器属性可以被打字稿看到,但同时也是一个很好的解决方案。

      【讨论】:

      • 我无法使UserFields 函数工作,但建议如下:function UserFields&lt;T extends new (...args: any[]) =&gt; {}&gt;(constructor: T) { return class extends constructor { name: string; email: string; } }
      • 看起来声明是等价的,如果能正常工作就很好了...
      • 我不确定,type Constructor 在您的示例中似乎未使用?也许是错字?
      • 哦,是的,错字肯定...应该是函数 UserFields.... 将编辑评论
      • 值得注意的是,在某些情况下复制静态成员很重要
      【解决方案3】:

      GitHub issue about this很多 的讨论。我认为它的总结是:装饰器 do not mutate class 的类型(大部分讨论是关于它是应该还是不应该 那样),因此你不能按照你想要的方式做,像这样:

      const decorator = (target: new (...args: any) => any) => {
        // note I'm extending target, not Person, otherwise you're not
        // decorating the passed-in thing
        return class New_Class extends target {
          myProp!: string
        }
      }
      
      @decorator
      class Person {
        noKnowledgeOfMyProp: this['myProp'] = "oops"; // error
      }
      
      declare const p: Person;
      p.myProp; // error, uh oh
      

      您可以做的只是将您的装饰器用作普通的mixin 函数,并让Person 扩展它的返回值。你最终有两个类定义......一个传递给decorator,另一个是你的新类扩展。 “内部”类(传递给decorator())仍然不知道添加的道具,但“外部”类知道:

      class Person extends decorator(class {
        innerProp: string = "inner";
        noKnowledgeOfMyProp: this['myProp'] = "oops"; // error
      }) {
        outerProp: string = "outer"
        hasKnowledgeOrMyProp: this['myProp'] = "okay"; // okay
      }
      
      declare const p: Person;
      p.myProp; // okay
      

      这有帮助吗?祝你好运!

      【讨论】:

      • 感谢您的回复@jcalz。我正在尝试构建一个框架,使其用户能够简单地将“@entity”装饰器添加到他们的模型中,因此您的解决方案语法不是那么用户友好。现在我有一个部分解决方案,我指示我的用户扩展一个“实体”抽象类,它声明了所有属性和方法。我希望找到一个更友好的解决方案,但我想目前不可能..
      猜你喜欢
      • 2018-05-08
      • 2021-07-07
      • 2016-08-18
      • 2017-03-06
      • 2019-09-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-22
      相关资源
      最近更新 更多