Los decoradores en TypeScript proporcionan un mecanismo poderoso para modificar el comportamiento de clases, métodos, propiedades y parámetros. Si bien pueden parecer una conveniencia moderna, los decoradores tienen sus raíces en el patrón de decorador bien establecido que se encuentra en la programación orientada a objetos. Al abstraer funciones comunes como el registro, la validación o el control de acceso, los decoradores permiten a los desarrolladores escribir código más limpio y fácil de mantener.
En este artículo, exploraremos los decoradores desde sus primeros principios, analizaremos su funcionalidad principal y los implementaremos desde cero. A lo largo del camino, veremos algunas aplicaciones del mundo real que muestran la utilidad de los decoradores en el desarrollo cotidiano de TypeScript.
En TypeScript, un decorador es simplemente una función que se puede adjuntar a una clase, método, propiedad o parámetro. Esta función se ejecuta en tiempo de diseño, lo que le brinda la posibilidad de modificar el comportamiento o la estructura del código antes de ejecutarlo. Los decoradores permiten la metaprogramación, lo que nos permite agregar funcionalidades adicionales sin modificar la lógica original.
Comencemos con un ejemplo simple de un decorador de métodos que registra cuando se llama a un método:
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { console.log(`Calling ${propertyKey} with arguments: ${args}`); return originalMethod.apply(this, args); }; return descriptor; } class Example { @log greet(name: string) { return `Hello, ${name}`; } } const example = new Example(); example.greet('John');
Aquí, el decorador de registros envuelve el método de bienvenida y registra su invocación y parámetros antes de ejecutarlo. Este patrón es útil para separar preocupaciones transversales, como el registro, de la lógica central.
Los decoradores en TypeScript son funciones que toman metadatos relacionados con el elemento que están decorando. Con base en estos metadatos (como prototipos de clases, nombres de métodos o descriptores de propiedades), los decoradores pueden modificar el comportamiento o incluso reemplazar el objeto decorado.
Los decoradores se pueden aplicar a varios objetivos, cada uno con diferentes propósitos:
function classDecorator(constructor: Function) { // Modify or extend the class constructor or prototype }
function methodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) { // Modify the method's descriptor }
function propertyDecorator(target: any, propertyKey: string) { // Modify the behavior of the property }
function parameterDecorator(target: any, propertyKey: string, parameterIndex: number) { // Modify or inspect the method's parameter }
Una de las características más poderosas de los decoradores es su capacidad para aceptar argumentos, lo que le permite personalizar su comportamiento. Por ejemplo, creemos un decorador de métodos que registre las llamadas a métodos de forma condicional en función de un argumento.
function logConditionally(shouldLog: boolean) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { if (shouldLog) { console.log(`Calling ${propertyKey} with arguments: ${args}`); } return originalMethod.apply(this, args); }; return descriptor; }; } class Example { @logConditionally(true) greet(name: string) { return `Hello, ${name}`; } } const example = new Example(); example.greet('TypeScript Developer');
Al pasar true al decorador logConditionally, nos aseguramos de que el método registre su ejecución. Si pasamos falso, se omite el registro. Esta flexibilidad es clave para que los decoradores sean versátiles y reutilizables.
Los decoradores han encontrado un uso práctico en muchas bibliotecas y marcos. A continuación se muestran algunos ejemplos notables que ilustran cómo los decoradores optimizan funciones complejas:
import { IsEmail, IsNotEmpty } from 'class-validator'; class User { @IsNotEmpty() name: string; @IsEmail() email: string; }
En este ejemplo, los decoradores @IsEmail y @IsNotEmpty garantizan que el campo de correo electrónico sea una dirección de correo electrónico válida y que el campo de nombre no esté vacío. Estos decoradores ahorran tiempo al eliminar la necesidad de una lógica de validación manual.
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; @Entity() class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() email: string; }
Aquí, @Entity, @Column y @PrimaryGeneratedColumn definen la estructura de la tabla de usuarios. Estos decoradores abstraen la complejidad de la creación de tablas SQL, lo que hace que el código sea más legible y fácil de mantener.
@Injectable({ providedIn: 'root', }) class UserService { constructor(private http: HttpClient) {} }
El decorador @Injectable en este caso indica al sistema de inyección de dependencia de Angular que el servicio de usuario debe proporcionarse globalmente. Esto permite una integración perfecta de los servicios en toda la aplicación.
Los decoradores son, en esencia, solo funciones. Analicemos el proceso de creación de decoradores desde cero:
Un decorador de clase recibe el constructor de la clase y puede usarse para modificar el prototipo de clase o incluso reemplazar el constructor.
function AddTimestamp(constructor: Function) { constructor.prototype.timestamp = new Date(); } @AddTimestamp class MyClass { id: number; constructor(id: number) { this.id = id; } } const instance = new MyClass(1); console.log(instance.timestamp); // Outputs the current timestamp
Un decorador de métodos modifica el descriptor del método, permitiéndole alterar el comportamiento del método en sí.
function logExecutionTime(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { const start = performance.now(); const result = originalMethod.apply(this, args); const end = performance.now(); console.log(`${propertyKey} executed in ${end - start}ms`); return result; }; return descriptor; } class Service { @logExecutionTime execute() { // Simulate work for (let i = 0; iDecorador de propiedades
Un decorador de propiedades le permite interceptar el acceso y la modificación de propiedades, lo que puede resultar útil para realizar un seguimiento de los cambios.
function trackChanges(target: any, propertyKey: string) { let value = target[propertyKey]; const getter = () => value; const setter = (newValue: any) => { console.log(`${propertyKey} changed from ${value} to ${newValue}`); value = newValue; }; Object.defineProperty(target, propertyKey, { get: getter, set: setter, }); } class Product { @trackChanges price: number; constructor(price: number) { this.price = price; } } const product = new Product(100); product.price = 200; // Logs the changeConclusión
Los decoradores en TypeScript le permiten abstraer y reutilizar funciones de una manera limpia y declarativa. Ya sea que esté trabajando con validación, ORM o inyección de dependencias, los decoradores ayudan a reducir el texto repetitivo y a mantener su código modular y mantenible. Comprender cómo funcionan desde los primeros principios hace que sea más fácil aprovechar todo su potencial y crear soluciones personalizadas adaptadas a su aplicación.
Al observar más profundamente la estructura y las aplicaciones del mundo real de los decoradores, ahora ha visto cómo pueden simplificar tareas de codificación complejas y optimizar el código en varios dominios.
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3