我正在寻找一种方法来减少我的NodeJS后端的样板代码。在Lombok中,例如可以通过对对象的注释来注入构造函数和getter/setter。
有没有办法在TypeScript中做到这一点?
我很快用谷歌搜索了一下,发现了这样的项目,它们试图将类似Lombok的功能引入TypeScript,但正如你所看到的,这些项目很少,也没有那么广泛使用。这意味着一个问题:你为什么想要这样的工具?
TS在减少样板方面已经相当不错了。当我定义一个类时,我通常这样做:
class A {
constructor(private fieldA: string, private readonly fieldB = 0) {}
}
这很简洁,不是吗?我想你是在比较TS和Java的能力。Java非常冗长,龙目岛对此有很大帮助。但是TS和JS是不同的,虽然龙目岛解决的一些问题已经由TS解决了,但其他问题在TS和JS的世界里不是问题。
首先,上面的语法创建了特定类型的类字段,带有访问修饰符,您还可以在field dB
前面找到readonly
关键字及其默认值0
。最重要的是,它们与构造函数一起创建,构造函数在执行时隐式地为实例字段赋值(参见,没有this. field dA=field dA
)。所以这已经超过了Lombok注入构造函数的能力。注意:在JS(因此在TS)中,您只能有一个构造函数。JS不支持方法重载。
关于getter/setter,它们在JS(或TS)中的使用方式与它们在Java中的使用方式不同。JS,您通常直接使用字段,setter和getter仅在您想要的特殊情况下使用:
所以你可以看到,龙目岛在Java处理的一些问题已经在TS处理了,其他的不是问题。
编辑9-2021-回答@Reijo的问题:Lomboks的功能超越了getter/setter/构造函数。查看@Builder注释,我对您对此的看法很感兴趣。
如果问题只是关于是否有一个TypeScript/JavaScript库提供与LombokJava相同的实用程序集合,据我所知,答案是否定的。我认为部分原因是TypeScript提供了开箱即用的功能(正如我在上面已经概述的那样),这让我回到了Java比TypeScript或Groovy等语言更需要Lombok的观点。当你需要TS不提供的东西时,比如构建器模式,你可以使用解决特定问题的库,比如构建器模式(在其核心使用JS代理),或者由于JS的灵活性(实际上TS),你可以轻松地自己编写。
这很好,但是你可能想以更声明性的方式添加功能——通过注释(在TS世界中,这些被称为装饰器,它们的工作方式不同),就像Lombok所做的那样。这可能会被证明很复杂。
首先,如果您在TS中通过装饰器修改类型,TS编译器无法识别更改。因此,如果您通过在装饰器中添加方法来扩充class,TS将无法识别该新方法。有关详细信息,请参阅此讨论。
这意味着你要么放弃装饰器,转而使用函数来修改类型(你可以),要么潜入AST。顺便说一句,Lombok就是这样工作的。它在称为注释处理的编译阶段接受带注释的类型,并且由于javac(和Eclipse编译器)中的黑客攻击,修改了它们的AST(例如。为给定的类创建一个内部构建器)。你可以用TS/JS的类似方式来做到这一点。
尽管在TS和JS中没有注释处理之类的东西,但您仍然可以创建一个构建步骤,该步骤获取源代码并修改它的AST以实现您的目标(Babel也是这样工作的)。这可能会导致向类添加方法,根据使用的注释(广义上-不一定是装饰器)生成构建器等。
不过,这种方法是一个挑战。除了AST是一个高级主题之外,即使你能让它工作,你也需要IDE的支持,这也意味着语言服务器的支持。这不适合胆小的人。
然而,如果你打算为TS创造像龙目岛这样的东西,我的编辑不应该吓跑任何人,因为似乎有些人希望在TS/JS世界看到它。它应该只向你展示未来;)。
我找到了@Getters和@Setters Lombok装饰器的替代品。
试试这个:
import { capitalize } from "lodash";
const Getters = () => <T extends {new(...args:any[]):{}}>(constructor:T) => {
return class extends constructor {
constructor(...args: any[]) {
super(...args);
const props = Reflect.ownKeys(this);
props.forEach((prop: string) => {
const capitalizedKey = capitalize(prop);
const methodName = `get${capitalizedKey}`;
Object.defineProperty(this, methodName, { value: () => this[prop] });
});
}
}
}
const Setters = () => <T extends {new(...args:any[]):{}}>(constructor:T) => {
return class extends constructor {
constructor(...args: any[]) {
super(...args);
const props = Reflect.ownKeys(this);
props.forEach((prop: string) => {
const capitalizedKey = capitalize(prop);
const methodName = `set${capitalizedKey}`;
Object.defineProperty(this, methodName, { value: (newValue: any) => { this[prop] = newValue } });
});
}
}
}
@Getters()
@Setters()
export class Person {
[x: string]: any;
nom: string;
prenom: string;
constructor(nom: string, prenom: string) {
this.nom = nom;
this.prenom = prenom;
}
}