Búsqueda de sitios web

javascript - ¿Existe algo como Lombok para TypeScript?


Estoy buscando una manera de reducir el código repetitivo en el backend de NodeJS. En Lombok, por ejemplo, existe la posibilidad de inyectar constructores y getters/setters mediante anotaciones para objetos.

¿Hay alguna manera de hacer esto en TypeScript?

Respuesta #1

Lo busqué rápidamente en Google y encontré proyectos como este que intentan llevar capacidades similares a Lombok a TypeScript, pero como puedes ver, esos proyectos son escasos y no tan ampliamente utilizados. Eso plantea una pregunta: ¿Por qué querrías una herramienta así?

TS ya es bastante bueno reduciendo el código repetitivo. Cuando defino una clase, generalmente lo hago así:

class A {
  constructor(private fieldA: string, private readonly fieldB = 0) {}
}

Esto es bastante conciso, ¿no? Supongo que estás comparando las capacidades de TS con las de Java. Java es muy expresivo con palabras y Lombok ayuda mucho con eso. Pero TS y JS son diferentes, y mientras algunos problemas, que Lombok resuelve, ya están resueltos por TS, otros no son un problema en el mundo de TS y JS.

En primer lugar, la sintaxis anterior crea campos de clase de ciertos tipos, con modificadores de acceso y también se puede observar la palabra clave readonly delante de fieldB y su valor por defecto 0. Además de eso, estos se crean junto con un constructor, que asigna implícitamente valores a los campos de instancia al ejecutarse (vea, no hay this.fieldA=fieldA). Así que esto ya cubre más que suficiente la capacidad de Lombok para inyectar constructores. Nota sobre esto: En JS (y por lo tanto en TS), solo se puede tener un único constructor. JS no soporta sobrecarga de métodos.

Ahora, sobre los getters/setters, estos no se usan de la misma manera en JS (o TS) que en Java. En JS, es norma trabajar con los campos directamente, y los setters y getters se usan solo en casos especiales donde quieras:

  1. Prohibir establecer un valor a la propiedad de un objeto en tiempo de ejecución definiendo solo un getter. Esto ahora suele ser un poco exagerado, y dado que usas TS, puedes simplemente declarar el campo como readonly y el compilador se asegurará de que no asignes a esa propiedad, sin necesidad de usar un getter. Si desarrollas en JS sin comprobaciones en tiempo de compilación, la convención es marcar las propiedades que son privadas (aquellas que definitivamente no deberías modificar) con un guion bajo. De cualquier manera, todavía puede suceder que modifiques una variable que no se supone que debas modificar, pero a diferencia de Java, esto no se considera una razón suficiente para usar get/set en todas partes en JS (y TS). En cambio, si realmente necesitas estar seguro de que no ocurran modificaciones en tiempo de ejecución, o bien usas el getter mencionado sin setter, o configuras la propiedad del objeto como no escribible.
  2. Tener una lógica personalizada en las funciones set/get es otra buena razón para utilizarlas. Un caso de uso común para esto es un getter que se calcula a partir de múltiples variables pero que aún quieres que se vea como un campo en un objeto. Eso se debe a que en JS, cuando invocas un getter, en realidad no usas () después del nombre del getter. Ahora, dado que esta lógica es personalizada, no puede generarse solo usando una anotación.

Así que, como puedes ver, algunos problemas que Lombok maneja en Java ya se manejan en TS y otros no son un problema.


Editar 5-9-2021 - respuesta a la pregunta de @Reijo: La funcionalidad de Lombok va más allá de getters/setters/constructores. Mirando la anotación @Builder, me interesa lo que dirías sobre esto.

Si la pregunta es solo sobre si existe una biblioteca de TypeScript/JavaScript que ofrezca más o menos la misma colección de utilidades que Lombok para Java, hasta donde sé, la respuesta es NO. Creo que en parte se debe a las capacidades que TypeScript proporciona de manera nativa (como ya mencioné arriba), lo que me lleva de nuevo al punto de que Java necesita Lombok más que lenguajes como TypeScript o Groovy. Cuando necesitas algo que TS no proporciona, como el patrón builder, puedes usar bibliotecas que resuelvan un problema particular, como builder-pattern (que utiliza JS Proxy en su núcleo) o, gracias a la naturaleza flexible de JS (y en efecto TS), escribirlo tú mismo fácilmente.

Todo eso está bien, pero quizás te gustaría agregar funcionalidad de manera más declarativa, mediante anotaciones (en el mundo de TS se llaman decoradores y funcionan de manera diferente), como lo hace Lombok. Esto podría resultar complejo.

Primero que nada, si modificas el tipo mediante un decorador en TS, el compilador de TS no reconoce el cambio. Así que si aumentas la clase agregando, digamos, un método a ella en tu decorador, TS no reconocerá ese nuevo método. Consulta esta discusión para más detalles.

Esto significa que o renuncias a los decoradores y usas funciones en su lugar para modificar el tipo (lo cual puedes hacer), o te sumerges en el AST. Por cierto, así es como funciona Lombok. Toma tipos anotados en una fase de compilación llamada procesamiento de anotaciones y, gracias a un truco en javac (y el compilador de Eclipse), modifica su AST (por ejemplo, para crear un constructor interno para una clase dada). Uno podría hacerlo de una manera algo similar con TS/JS.

Aunque no existe nada como el procesamiento de anotaciones en TS ni en JS como tal, aún podrías crear un paso de compilación que tome un código fuente y modifique su AST para lograr tus objetivos (que es como funciona también Babel). Esto podría resultar en agregar un método a una clase, generar un constructor, etc., basado en una anotación (en un sentido amplio, no necesariamente un decorador) utilizada.

Sin embargo, este enfoque es un desafío. Además de que AST es un tema avanzado, incluso si logras que funcione, necesitarías soporte de tu IDE, lo que hoy en día también significa soporte de los servidores de lenguaje. Y eso no es para los débiles de corazón.

Sin embargo, mi edición no tiene la intención de asustar a nadie si planeas crear algo como Lombok para TS, ya que parece que a bastante gente le gustaría verlo en el mundo de TS/JS. Solo debería mostrarte lo que se avecina ;).

Respuesta #2

Encontré una alternativa a los decoradores @Getters y @Setters de Lombok.

Prueba esto:

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;
  }
}

Artículos relacionados