Volver a TypeScript Intermedio

Decoradores en TypeScript

Los decoradores son una propuesta en etapa 3 que TypeScript soporta de forma nativa. Permiten anotar y modificar clases, métodos y propiedades.

Habilitar con "experimentalDecorators": true (legado) o usar los decoradores nativos de TypeScript 5.0+.

Decoradores de Clase

// Añade una propiedad 'version' a la clase function versionado(version: string) { return function <T extends new (...args: any[]) => {}>(Base: T) { return class extends Base { version = version; }; }; } // Sella la clase (impide añadir nuevas propiedades en tiempo de ejecución) function sellado(constructor: Function) { Object.seal(constructor); Object.seal(constructor.prototype); } @versionado('1.0.0') @sellado class App { nombre = 'MiApp'; } const app = new App(); console.log((app as any).version); // '1.0.0'

Decoradores de Método

// Registrar llamadas a métodos function registrar(target: any, clave: string, descriptor: PropertyDescriptor) { const original = descriptor.value; descriptor.value = function (...args: any[]) { console.log(`Llamando a ${clave} con`, args); const resultado = original.apply(this, args); console.log(`${clave} retornó`, resultado); return resultado; }; return descriptor; } // Cachear resultados de métodos function memoizar(target: any, clave: string, descriptor: PropertyDescriptor) { const original = descriptor.value; const cache = new Map<string, any>(); descriptor.value = function (...args: any[]) { const claveCaché = JSON.stringify(args); if (cache.has(claveCaché)) return cache.get(claveCaché); const resultado = original.apply(this, args); cache.set(claveCaché, resultado); return resultado; }; return descriptor; } class ServicioMatemático { @registrar @memoizar fibonacci(n: number): number { if (n <= 1) return n; return this.fibonacci(n - 1) + this.fibonacci(n - 2); } }

Decoradores de Propiedad

// Validar que una propiedad sea un número positivo function positivo(target: any, clave: string) { let valor: number; Object.defineProperty(target, clave, { get: () => valor, set: (nuevoValor: number) => { if (nuevoValor <= 0) throw new Error(`${clave} debe ser positivo`); valor = nuevoValor; }, enumerable: true, configurable: true, }); } class Producto { @positivo precio!: number; nombre: string; constructor(nombre: string, precio: number) { this.nombre = nombre; this.precio = precio; // Activa el setter — valida } } const producto = new Producto('Widget', 9.99); // new Producto('Malo', -1); // ❌ Error: precio debe ser positivo

Decoradores de Parámetro + Reflect Metadata

import 'reflect-metadata'; function validar(target: any, clave: string, índice: number) { const existentes = Reflect.getMetadata('requerido', target, clave) ?? []; Reflect.defineMetadata('requerido', [...existentes, índice], target, clave); } function validarParams(target: any, clave: string, descriptor: PropertyDescriptor) { const original = descriptor.value; descriptor.value = function (...args: any[]) { const requeridos: number[] = Reflect.getMetadata('requerido', target, clave) ?? []; requeridos.forEach(idx => { if (args[idx] == null) throw new Error(`El argumento en índice ${idx} es requerido`); }); return original.apply(this, args); }; return descriptor; } class ServicioUsuario { @validarParams crearUsuario(@validar nombre: string, @validar correo: string) { return { nombre, correo }; } }