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