Español

Explore el poder de los decoradores de JavaScript para la gestión de metadatos y la modificación de código. Aprenda a mejorar su código con claridad y eficiencia, con las mejores prácticas internacionales.

Decoradores de JavaScript: Liberando el Poder de los Metadatos y la Modificación de Código

Los decoradores de JavaScript ofrecen una forma potente y elegante de añadir metadatos y modificar el comportamiento de clases, métodos, propiedades y parámetros. Proporcionan una sintaxis declarativa para mejorar el código con intereses transversales como el registro, la validación, la autorización y más. Aunque todavía son una característica relativamente nueva, los decoradores están ganando popularidad, especialmente en TypeScript, y prometen mejorar la legibilidad, mantenibilidad y reutilización del código. Este artículo explora las capacidades de los decoradores de JavaScript, proporcionando ejemplos prácticos y conocimientos para desarrolladores de todo el mundo.

¿Qué son los decoradores de JavaScript?

Los decoradores son esencialmente funciones que envuelven a otras funciones o clases. Proporcionan una forma de modificar o mejorar el comportamiento del elemento decorado sin alterar directamente su código original. Los decoradores usan el símbolo @ seguido de un nombre de función para decorar clases, métodos, accesores, propiedades o parámetros.

Considérelos como azúcar sintáctico para funciones de orden superior, ofreciendo una forma más limpia y legible de aplicar intereses transversales a su código. Los decoradores le permiten separar responsabilidades de manera efectiva, lo que conduce a aplicaciones más modulares y mantenibles.

Tipos de Decoradores

Los decoradores de JavaScript vienen en varias formas, cada una dirigida a diferentes elementos de su código:

Sintaxis Básica

La sintaxis para aplicar un decorador es sencilla:

@decoratorName
class MyClass {
  @methodDecorator
  myMethod( @parameterDecorator param: string ) {
    @propertyDecorator
    myProperty: number;
  }
}

Aquí hay un desglose:

Decoradores de Clase: Modificando el Comportamiento de la Clase

Los decoradores de clase son funciones que reciben el constructor de la clase como argumento. Pueden ser utilizados para:

Ejemplo: Registrar la Creación de Clases

Imagine que desea registrar cada vez que se crea una nueva instancia de una clase. Un decorador de clase puede lograr esto:

function logClassCreation(constructor: Function) {
  return class extends constructor {
    constructor(...args: any[]) {
      console.log(`Creando una nueva instancia de ${constructor.name}`);
      super(...args);
    }
  };
}

@logClassCreation
class User {
  name: string;

  constructor(name: string) {
    this.name = name;
  }
}

const user = new User("Alice"); // Salida: Creando una nueva instancia de User

En este ejemplo, logClassCreation reemplaza la clase User original con una nueva clase que la extiende. El constructor de la nueva clase registra un mensaje y luego llama al constructor original usando super.

Decoradores de Método: Mejorando la Funcionalidad del Método

Los decoradores de método reciben tres argumentos:

Pueden ser utilizados para:

Ejemplo: Registrar Llamadas a Métodos

Vamos a crear un decorador de método que registre cada vez que se llama a un método, junto con sus argumentos:

function logMethodCall(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`Llamando al método ${propertyKey} con argumentos: ${JSON.stringify(args)}`);
    const result = originalMethod.apply(this, args);
    console.log(`El método ${propertyKey} devolvió: ${result}`);
    return result;
  };

  return descriptor;
}

class Calculator {
  @logMethodCall
  add(x: number, y: number): number {
    return x + y;
  }
}

const calculator = new Calculator();
const sum = calculator.add(5, 3); // Salida: Llamando al método add con argumentos: [5,3]
                                 //         El método add devolvió: 8

El decorador logMethodCall envuelve el método original. Antes de ejecutar el método original, registra el nombre del método y los argumentos. Después de la ejecución, registra el valor devuelto.

Decoradores de Accesor: Controlando el Acceso a la Propiedad

Los decoradores de accesor son similares a los decoradores de método pero se aplican específicamente a los métodos getter y setter (accesores). Reciben los mismos tres argumentos que los decoradores de método:

Pueden ser utilizados para:

Ejemplo: Validar Valores del Setter

Vamos a crear un decorador de accesor que valide el valor que se está estableciendo para una propiedad:

function validateAge(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalSet = descriptor.set;

  descriptor.set = function (value: number) {
    if (value < 0) {
      throw new Error("La edad no puede ser negativa");
    }
    originalSet.call(this, value);
  };

  return descriptor;
}

class Person {
  private _age: number;

  @validateAge
  set age(value: number) {
    this._age = value;
  }

  get age(): number {
    return this._age;
  }
}

const person = new Person();
person.age = 30; // Funciona bien

try {
  person.age = -5; // Lanza un error: La edad no puede ser negativa
} catch (error:any) {
  console.error(error.message);
}

El decorador validateAge intercepta el setter de la propiedad age. Comprueba si el valor es negativo y lanza un error si lo es. De lo contrario, llama al setter original.

Decoradores de Propiedad: Modificando Descriptores de Propiedad

Los decoradores de propiedad reciben dos argumentos:

Pueden ser utilizados para:

Ejemplo: Hacer una Propiedad de Solo Lectura

Vamos a crear un decorador de propiedad que haga una propiedad de solo lectura:

function readOnly(target: any, propertyKey: string) {
  Object.defineProperty(target, propertyKey, {
    writable: false,
  });
}

class Configuration {
  @readOnly
  apiUrl: string = "https://api.example.com";
}

const config = new Configuration();

try {
  (config as any).apiUrl = "https://newapi.example.com"; // Lanza un error en modo estricto
  console.log(config.apiUrl); // Salida: https://api.example.com
} catch (error) {
  console.error("No se puede asignar a la propiedad de solo lectura 'apiUrl' del objeto '#'", error);
}

El decorador readOnly utiliza Object.defineProperty para modificar el descriptor de la propiedad, estableciendo writable en false. Intentar modificar la propiedad ahora resultará en un error (en modo estricto) o será ignorado.

Decoradores de Parámetro: Proporcionando Metadatos sobre Parámetros

Los decoradores de parámetro reciben tres argumentos:

Los decoradores de parámetro se usan con menos frecuencia que otros tipos, but pueden ser útiles para escenarios donde se necesita asociar metadatos con parámetros específicos.

Ejemplo: Inyección de Dependencias

Los decoradores de parámetro se pueden utilizar en frameworks de inyección de dependencias para identificar las dependencias que deben ser inyectadas en un método. Aunque un sistema completo de inyección de dependencias está fuera del alcance de este artículo, aquí hay una ilustración simplificada:

const dependencies: any[] = [];

function inject(token: any) {
  return function (target: any, propertyKey: string | symbol, parameterIndex: number) {
    dependencies.push({
      target,
      propertyKey,
      parameterIndex,
      token,
    });
  };
}

class UserService {
  getUser(id: number) {
    return `Usuario con ID ${id}`;
  }
}

class UserController {
  private userService: UserService;

  constructor(@inject(UserService) userService: UserService) {
    this.userService = userService;
  }

  getUser(id: number) {
    return this.userService.getUser(id);
  }
}

//Recuperación simplificada de las dependencias
const userServiceInstance = new UserService();
const userController = new UserController(userServiceInstance);
console.log(userController.getUser(123)); // Salida: Usuario con ID 123

En este ejemplo, el decorador @inject almacena metadatos sobre el parámetro userService en el array dependencies. Un contenedor de inyección de dependencias podría entonces usar estos metadatos para resolver e inyectar la dependencia apropiada.

Aplicaciones Prácticas y Casos de Uso

Los decoradores se pueden aplicar a una amplia variedad de escenarios para mejorar la calidad y la mantenibilidad del código:

Beneficios de Usar Decoradores

Los decoradores ofrecen varias ventajas clave:

Consideraciones y Mejores Prácticas

Decoradores en Diferentes Entornos

Aunque los decoradores son parte de la especificación ESNext, su soporte varía en los diferentes entornos de JavaScript:

Perspectivas Globales sobre los Decoradores

La adopción de decoradores varía en las diferentes regiones y comunidades de desarrollo. En algunas regiones, donde TypeScript es ampliamente adoptado (p. ej., partes de América del Norte y Europa), los decoradores se utilizan comúnmente. En otras regiones, donde JavaScript es más prevalente o donde los desarrolladores prefieren patrones más simples, los decoradores pueden ser menos comunes.

Además, el uso de patrones de decoradores específicos puede variar según las preferencias culturales y los estándares de la industria. Por ejemplo, en algunas culturas, se prefiere un estilo de codificación más detallado y explícito, mientras que en otras, se favorece un estilo más conciso y expresivo.

Al trabajar en proyectos internacionales, es esencial considerar estas diferencias culturales y regionales y establecer estándares de codificación que sean claros, concisos y fácilmente comprensibles por todos los miembros del equipo. Esto puede implicar proporcionar documentación adicional, capacitación o tutoría para garantizar que todos se sientan cómodos usando decoradores.

Conclusión

Los decoradores de JavaScript son una herramienta poderosa para mejorar el código con metadatos y modificar el comportamiento. Al comprender los diferentes tipos de decoradores y sus aplicaciones prácticas, los desarrolladores pueden escribir código más limpio, mantenible y reutilizable. A medida que los decoradores ganan una adopción más amplia, están destinados a convertirse en una parte esencial del panorama del desarrollo de JavaScript. Adopte esta potente característica y libere su potencial para elevar su código a nuevas alturas. Recuerde seguir siempre las mejores prácticas y considerar las implicaciones de rendimiento del uso de decoradores en sus aplicaciones. Con una planificación e implementación cuidadosas, los decoradores pueden mejorar significativamente la calidad y la mantenibilidad de sus proyectos de JavaScript. ¡Feliz codificación!