使用 TypeScript 装饰器扩展 ES6 类时扩展类型
Posted
技术标签:
【中文标题】使用 TypeScript 装饰器扩展 ES6 类时扩展类型【英文标题】:Extending type when extending an ES6 class with a TypeScript decorator 【发布时间】: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 类并保留类型完成、类型安全等?
【问题讨论】:
【参考方案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<T extends new (...args: any[]) => >(constructor: T) return class extends constructor name: string; email: string;
看起来声明是等价的,如果能正常工作就很好了...
我不确定,type Constructor
在您的示例中似乎未使用?也许是错字?
哦,是的,错字肯定...应该是函数 UserFieldsGitHub 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”装饰器添加到他们的模型中,因此您的解决方案语法不是那么用户友好。现在我有一个部分解决方案,我指示我的用户扩展一个“实体”抽象类,它声明了所有属性和方法。我希望找到一个更友好的解决方案,但我想目前不可能..以上是关于使用 TypeScript 装饰器扩展 ES6 类时扩展类型的主要内容,如果未能解决你的问题,请参考以下文章