TypeScript
Tutorial TypeScript: Decoradores básicos
Aprende los fundamentos de los decoradores en TypeScript con ejemplos prácticos para modificar clases y métodos de forma declarativa.
Aprende TypeScript y certifícate¿Qué son decoradores?
Los decoradores son un patrón de diseño que permite modificar o extender el comportamiento de clases, métodos, propiedades o parámetros sin alterar su código fuente original. En TypeScript, los decoradores son una característica experimental que proporciona una forma elegante de añadir metadatos o transformar elementos de código de manera declarativa.
Un decorador es esencialmente una función especial que se aplica a una declaración mediante una sintaxis específica. Se coloca justo antes del elemento que queremos decorar, precedido por el símbolo @
. Esta sintaxis declarativa hace que el código sea más legible y mantenible.
Estructura básica de un decorador
La estructura básica de un decorador en TypeScript es una función que recibe ciertos parámetros dependiendo del tipo de elemento que está decorando:
function miDecorador(target: any, propertyKey?: string, descriptor?: PropertyDescriptor) {
// Lógica del decorador
}
// Uso del decorador
@miDecorador
class MiClase {
// ...
}
Tipos de decoradores
En TypeScript podemos crear diferentes tipos de decoradores según el elemento que queramos modificar:
- Decoradores de clase: Se aplican a la definición de una clase.
- Decoradores de método: Se aplican a un método dentro de una clase.
- Decoradores de propiedad: Se aplican a una propiedad de una clase.
- Decoradores de parámetro: Se aplican a los parámetros de un método o constructor.
- Decoradores de accesores: Se aplican a los getters y setters.
Habilitando decoradores en TypeScript
Para utilizar decoradores en TypeScript, necesitamos habilitar la característica en nuestro archivo tsconfig.json
:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
Creando un decorador simple
Veamos un ejemplo sencillo de un decorador de clase que añade una propiedad a la clase:
function AgregarVersion(version: string) {
return function(constructor: Function) {
constructor.prototype.version = version;
};
}
@AgregarVersion("1.0.0")
class MiAplicacion {
nombre: string;
constructor(nombre: string) {
this.nombre = nombre;
}
}
// Ahora podemos acceder a la propiedad version
const app = new MiAplicacion("Mi App");
console.log((app as any).version); // Salida: 1.0.0
En este ejemplo, el decorador AgregarVersion
es una función de fábrica que devuelve la función decoradora real. Esto nos permite pasar parámetros al decorador.
Decoradores de fábrica
Los decoradores de fábrica son funciones que retornan la función decoradora real. Esto nos permite personalizar cómo se comportará el decorador:
function Log(mensaje: string) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const metodoOriginal = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`${mensaje}: ${propertyKey}`);
return metodoOriginal.apply(this, args);
};
return descriptor;
};
}
class Calculadora {
@Log("Método ejecutado")
sumar(a: number, b: number): number {
return a + b;
}
}
const calc = new Calculadora();
calc.sumar(5, 3); // Salida: Método ejecutado: sumar
// Resultado: 8
Composición de decoradores
Podemos aplicar múltiples decoradores a un mismo elemento. La evaluación de los decoradores se realiza de arriba hacia abajo, pero la aplicación se realiza de abajo hacia arriba:
function primero() {
console.log("primero(): evaluado");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("primero(): llamado");
};
}
function segundo() {
console.log("segundo(): evaluado");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("segundo(): llamado");
};
}
class Ejemplo {
@primero()
@segundo()
metodo() {}
}
// Salida:
// primero(): evaluado
// segundo(): evaluado
// segundo(): llamado
// primero(): llamado
Casos de uso prácticos
Los decoradores son especialmente útiles en varios escenarios:
- Validación de datos: Validar parámetros o propiedades.
function ValidarLongitud(min: number, max: number) {
return function(target: any, propertyKey: string) {
let value: string;
const getter = function() {
return value;
};
const setter = function(newVal: string) {
if (newVal.length < min || newVal.length > max) {
throw new Error(`La longitud debe estar entre ${min} y ${max}`);
}
value = newVal;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter
});
};
}
class Usuario {
@ValidarLongitud(3, 10)
nombre: string;
constructor(nombre: string) {
this.nombre = nombre;
}
}
// Esto funcionará
const usuario1 = new Usuario("Ana");
// Esto lanzará un error
try {
const usuario2 = new Usuario("A");
} catch (e) {
console.error(e.message); // La longitud debe estar entre 3 y 10
}
- Registro y monitoreo: Registrar información sobre la ejecución de métodos.
function Registrar(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const metodoOriginal = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Llamada a ${propertyKey} con argumentos: ${JSON.stringify(args)}`);
const resultado = metodoOriginal.apply(this, args);
console.log(`${propertyKey} retornó: ${JSON.stringify(resultado)}`);
return resultado;
};
return descriptor;
}
class Servicio {
@Registrar
obtenerDatos(id: number) {
return { id, nombre: "Producto " + id };
}
}
const servicio = new Servicio();
servicio.obtenerDatos(123);
// Salida:
// Llamada a obtenerDatos con argumentos: [123]
// obtenerDatos retornó: {"id":123,"nombre":"Producto 123"}
- Inyección de dependencias: Implementar sistemas de inyección de dependencias.
const dependencias = new Map<string, any>();
function Inyectar(clave: string) {
return function(target: any, propertyKey: string) {
const getter = function() {
return dependencias.get(clave);
};
Object.defineProperty(target, propertyKey, {
get: getter
});
};
}
// Registramos una dependencia
class ServicioHttp {
get(url: string) {
return `Datos de ${url}`;
}
}
dependencias.set("http", new ServicioHttp());
class Componente {
@Inyectar("http")
private http: ServicioHttp;
cargarDatos() {
return this.http.get("https://api.ejemplo.com/datos");
}
}
const componente = new Componente();
console.log(componente.cargarDatos()); // Datos de https://api.ejemplo.com/datos
Limitaciones y consideraciones
Al trabajar con decoradores en TypeScript, es importante tener en cuenta algunas limitaciones:
- Los decoradores son una característica experimental y su API podría cambiar en futuras versiones.
- No todos los entornos de ejecución soportan decoradores nativamente, por lo que es necesario transpilación.
- Los decoradores se ejecutan solo una vez, cuando la clase es definida, no cada vez que se crea una instancia.
function Imprimir(target: any) {
console.log("Decorador ejecutado");
}
@Imprimir
class Ejemplo {
constructor() {
console.log("Constructor ejecutado");
}
}
// Salida al definir la clase:
// Decorador ejecutado
// Salida al crear instancias:
const e1 = new Ejemplo(); // Constructor ejecutado
const e2 = new Ejemplo(); // Constructor ejecutado
Los decoradores en TypeScript proporcionan una forma elegante y declarativa de extender y modificar el comportamiento de nuestro código, permitiéndonos implementar patrones complejos de manera más limpia y mantenible.
Decoradores de clase
Los decoradores de clase son uno de los tipos más comunes y útiles en TypeScript. Se aplican directamente a la declaración de una clase y permiten modificar o extender su comportamiento. Estos decoradores reciben como parámetro el constructor de la clase, lo que nos permite manipular tanto la clase en sí como su prototipo.
Estructura básica
Un decorador de clase tiene la siguiente estructura:
function decoradorClase(constructor: Function) {
// Modificaciones a la clase
}
@decoradorClase
class MiClase {
// Propiedades y métodos
}
El parámetro constructor
representa la función constructora de la clase decorada, lo que nos permite acceder a su prototipo, añadir propiedades, modificar métodos o incluso reemplazar la clase por completo.
Modificando el prototipo de la clase
Una de las aplicaciones más comunes de los decoradores de clase es añadir propiedades o métodos al prototipo:
function AgregarMetodo(constructor: Function) {
constructor.prototype.saludar = function() {
return "¡Hola desde el decorador!";
};
}
@AgregarMetodo
class Persona {
nombre: string;
constructor(nombre: string) {
this.nombre = nombre;
}
}
// Necesitamos usar casting porque TypeScript no conoce el método añadido dinámicamente
const persona = new Persona("Ana");
console.log((persona as any).saludar()); // ¡Hola desde el decorador!
Decoradores de clase con parámetros
Para crear decoradores más flexibles, podemos implementar decoradores de fábrica que acepten parámetros:
function Componente(opciones: { selector: string, template: string }) {
return function(constructor: Function) {
constructor.prototype.opciones = opciones;
constructor.prototype.renderizar = function() {
const elemento = document.querySelector(opciones.selector);
if (elemento) {
elemento.innerHTML = opciones.template;
}
};
};
}
@Componente({
selector: '#mi-componente',
template: '<h1>Componente personalizado</h1>'
})
class MiComponente {
constructor() {
// Lógica del componente
}
}
const componente = new MiComponente();
(componente as any).renderizar();
Este patrón es muy similar al utilizado en frameworks como Angular para definir componentes.
Reemplazando la clase original
Los decoradores de clase también pueden reemplazar completamente la clase original devolviendo una nueva clase que extiende o modifica la original:
function Sellado<T extends { new(...args: any[]): {} }>(constructor: T) {
// Devolvemos una nueva clase que extiende la original
return class extends constructor {
sellado = true;
constructor(...args: any[]) {
super(...args);
console.log("Se ha creado una instancia sellada");
}
};
}
@Sellado
class Documento {
contenido: string;
constructor(contenido: string) {
this.contenido = contenido;
}
}
const doc = new Documento("Contenido confidencial");
console.log(doc); // { contenido: "Contenido confidencial", sellado: true }
En este ejemplo, el decorador Sellado
devuelve una nueva clase que extiende la original, añadiendo una propiedad sellado
y modificando el constructor para mostrar un mensaje.
Decoradores de clase para metaprogramación
Los decoradores de clase son especialmente útiles para implementar técnicas de metaprogramación, como registrar clases en un registro central:
const registroClases: Record<string, Function> = {};
function Registrar(nombre: string) {
return function(constructor: Function) {
registroClases[nombre] = constructor;
console.log(`Clase ${nombre} registrada`);
};
}
@Registrar("Usuario")
class Usuario {
constructor(public nombre: string, public email: string) {}
}
@Registrar("Producto")
class Producto {
constructor(public nombre: string, public precio: number) {}
}
// Podemos crear instancias dinámicamente desde el registro
function crearInstancia(nombre: string, ...args: any[]) {
const Constructor = registroClases[nombre];
return Constructor ? new Constructor(...args) : null;
}
const usuario = crearInstancia("Usuario", "Juan", "juan@ejemplo.com");
console.log(usuario); // Usuario { nombre: "Juan", email: "juan@ejemplo.com" }
Decoradores para validación de clases
Podemos usar decoradores de clase para validar que una clase cumpla con ciertos requisitos:
function RequiereImplementacion(...interfaces: string[]) {
return function(constructor: Function) {
const prototype = constructor.prototype;
interfaces.forEach(interfaz => {
switch(interfaz) {
case "Serializable":
if (!prototype.serializar || !prototype.deserializar) {
throw new Error(`La clase ${constructor.name} debe implementar serializar() y deserializar()`);
}
break;
case "Validable":
if (!prototype.validar) {
throw new Error(`La clase ${constructor.name} debe implementar validar()`);
}
break;
}
});
};
}
@RequiereImplementacion("Serializable")
class DatosUsuario {
constructor(public nombre: string, public edad: number) {}
// Falta implementar serializar() y deserializar()
}
// Esto lanzará un error al cargar el módulo:
// Error: La clase DatosUsuario debe implementar serializar() y deserializar()
Decoradores de clase para singleton
Un patrón común es usar decoradores para implementar el patrón singleton:
function Singleton<T extends { new(...args: any[]): {} }>(constructor: T) {
// Guardamos la instancia original
const original = constructor;
// Función que creará o devolverá la instancia única
function construir(...args: any[]): T {
// Creamos una propiedad estática en el constructor
if (!constructor.prototype.instanciaUnica) {
constructor.prototype.instanciaUnica = new original(...args);
}
return constructor.prototype.instanciaUnica;
}
// Reemplazamos el constructor
const nuevoConstructor: any = function(...args: any[]) {
return construir(...args);
};
// Copiamos el prototipo
nuevoConstructor.prototype = original.prototype;
return nuevoConstructor as T;
}
@Singleton
class BaseDatos {
private conexion: string;
constructor(url: string) {
this.conexion = url;
console.log(`Conectando a ${url}...`);
}
ejecutarConsulta(sql: string) {
console.log(`Ejecutando ${sql} en ${this.conexion}`);
}
}
// Solo se mostrará "Conectando..." una vez
const db1 = new BaseDatos("mongodb://localhost:27017");
const db2 = new BaseDatos("mongodb://localhost:27017");
console.log(db1 === db2); // true - Es la misma instancia
Combinando decoradores de clase
Podemos aplicar múltiples decoradores a una misma clase para combinar funcionalidades:
function Loggeable(constructor: Function) {
constructor.prototype.log = function(mensaje: string) {
console.log(`[${constructor.name}] ${mensaje}`);
};
}
function Versionado(version: string) {
return function(constructor: Function) {
constructor.prototype.version = version;
};
}
@Loggeable
@Versionado("1.2.3")
class Servicio {
constructor() {
(this as any).log(`Servicio inicializado, versión ${(this as any).version}`);
}
ejecutar() {
(this as any).log("Ejecutando operación");
}
}
const servicio = new Servicio();
// [Servicio] Servicio inicializado, versión 1.2.3
servicio.ejecutar();
// [Servicio] Ejecutando operación
Consideraciones de rendimiento
Es importante tener en cuenta que los decoradores de clase se ejecutan durante la definición de la clase, no durante la creación de instancias. Esto significa que operaciones costosas en decoradores solo afectan al tiempo de carga del módulo, no al rendimiento en tiempo de ejecución:
function Analizar(constructor: Function) {
console.time("Análisis de clase");
// Análisis de la estructura de la clase
const propiedades = Object.getOwnPropertyNames(constructor.prototype);
console.log(`La clase ${constructor.name} tiene ${propiedades.length} métodos`);
console.timeEnd("Análisis de clase");
}
@Analizar
class ComponenteComplejo {
// Múltiples métodos y propiedades
metodo1() {}
metodo2() {}
// ...
}
// El análisis se ejecuta al cargar el módulo, no al crear instancias
const c1 = new ComponenteComplejo(); // No ejecuta el análisis nuevamente
const c2 = new ComponenteComplejo(); // No ejecuta el análisis nuevamente
Los decoradores de clase son una herramienta potente para implementar patrones de diseño complejos y extender la funcionalidad de nuestras clases de manera declarativa, mejorando la legibilidad y mantenibilidad del código.
Decoradores de método
Los decoradores de método son funciones especiales que se aplican a los métodos de una clase para modificar o extender su comportamiento. A diferencia de los decoradores de clase que afectan a toda la clase, los decoradores de método se centran específicamente en alterar el comportamiento de un método individual.
Estructura básica
Un decorador de método recibe tres parámetros:
function decoradorMetodo(
target: any, // La clase prototipo
propertyKey: string, // El nombre del método
descriptor: PropertyDescriptor // El descriptor de propiedad del método
) {
// Modificaciones al método
}
El parámetro descriptor
es especialmente importante, ya que contiene la definición del método y nos permite modificar su comportamiento:
{
value: Function, // La función original del método
writable: boolean, // Si se puede sobrescribir
enumerable: boolean, // Si aparece en enumeraciones (ej: for...in)
configurable: boolean // Si se puede modificar o eliminar
}
Ejemplo básico de un decorador de método
Veamos un ejemplo sencillo que registra cuándo se llama a un método:
function Registrar(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
// Guardamos referencia al método original
const metodoOriginal = descriptor.value;
// Reemplazamos el método con nuestra versión modificada
descriptor.value = function(...args: any[]) {
console.log(`Método ${propertyKey} llamado a las ${new Date().toLocaleTimeString()}`);
// Llamamos al método original y devolvemos su resultado
return metodoOriginal.apply(this, args);
};
return descriptor;
}
class Calculadora {
@Registrar
sumar(a: number, b: number): number {
return a + b;
}
}
const calc = new Calculadora();
console.log(calc.sumar(5, 3));
// Salida:
// Método sumar llamado a las 12:34:56
// 8
Decoradores de método con parámetros
Para crear decoradores más flexibles, podemos implementar decoradores de fábrica que acepten parámetros:
function Repetir(veces: number) {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const metodoOriginal = descriptor.value;
descriptor.value = function(...args: any[]) {
let resultado: any;
for (let i = 0; i < veces; i++) {
resultado = metodoOriginal.apply(this, args);
console.log(`Ejecución ${i + 1}: ${resultado}`);
}
return resultado;
};
return descriptor;
};
}
class Procesador {
@Repetir(3)
procesar(dato: string): string {
return dato.toUpperCase();
}
}
const proc = new Procesador();
proc.procesar("hola");
// Salida:
// Ejecución 1: HOLA
// Ejecución 2: HOLA
// Ejecución 3: HOLA
Decoradores para validación de parámetros
Los decoradores de método son ideales para implementar validaciones de parámetros:
function ValidarNoVacio(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const metodoOriginal = descriptor.value;
descriptor.value = function(...args: any[]) {
// Verificamos que ningún argumento sea vacío
for (let i = 0; i < args.length; i++) {
if (args[i] === "" || args[i] === null || args[i] === undefined) {
throw new Error(`El parámetro #${i + 1} de ${propertyKey} no puede estar vacío`);
}
}
return metodoOriginal.apply(this, args);
};
return descriptor;
}
class Formulario {
@ValidarNoVacio
enviar(nombre: string, email: string) {
console.log(`Formulario enviado: ${nombre}, ${email}`);
return true;
}
}
const form = new Formulario();
form.enviar("Juan", "juan@ejemplo.com"); // Funciona correctamente
try {
form.enviar("", "correo@ejemplo.com");
} catch (error) {
console.error(error.message); // El parámetro #1 de enviar no puede estar vacío
}
Decoradores para control de acceso
Podemos usar decoradores para implementar control de acceso a métodos:
function SoloAdmin(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const metodoOriginal = descriptor.value;
descriptor.value = function(...args: any[]) {
// Verificamos si el usuario actual es administrador
if (!(this as any).esAdmin) {
throw new Error("Acceso denegado: Se requieren permisos de administrador");
}
return metodoOriginal.apply(this, args);
};
return descriptor;
}
class Sistema {
esAdmin: boolean;
constructor(esAdmin: boolean) {
this.esAdmin = esAdmin;
}
@SoloAdmin
configurarSistema() {
console.log("Sistema configurado correctamente");
}
}
const usuarioNormal = new Sistema(false);
const usuarioAdmin = new Sistema(true);
try {
usuarioNormal.configurarSistema(); // Error
} catch (error) {
console.error(error.message); // Acceso denegado: Se requieren permisos de administrador
}
usuarioAdmin.configurarSistema(); // Sistema configurado correctamente
Decoradores para manejo de errores
Los decoradores de método son excelentes para implementar manejo de errores centralizado:
function ManejarErrores(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const metodoOriginal = descriptor.value;
descriptor.value = function(...args: any[]) {
try {
return metodoOriginal.apply(this, args);
} catch (error) {
console.error(`Error en ${propertyKey}: ${error.message}`);
// Podríamos registrar el error, notificar, etc.
return null; // Valor por defecto en caso de error
}
};
return descriptor;
}
class ServicioAPI {
@ManejarErrores
obtenerDatos(id: number) {
if (id <= 0) {
throw new Error("ID no válido");
}
return { id, nombre: "Producto " + id };
}
}
const servicio = new ServicioAPI();
console.log(servicio.obtenerDatos(1)); // { id: 1, nombre: "Producto 1" }
console.log(servicio.obtenerDatos(-5)); // Error en obtenerDatos: ID no válido
// null
Decoradores para caché de resultados
Podemos implementar un sistema de caché para métodos que realizan operaciones costosas:
function Cachear(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const metodoOriginal = descriptor.value;
const cacheKey = Symbol("cache");
descriptor.value = function(...args: any[]) {
// Creamos un caché para esta instancia si no existe
this[cacheKey] = this[cacheKey] || {};
// Creamos una clave única basada en los argumentos
const key = JSON.stringify(args);
// Si el resultado está en caché, lo devolvemos
if (key in this[cacheKey]) {
console.log(`Usando resultado en caché para ${propertyKey}(${args})`);
return this[cacheKey][key];
}
// Si no está en caché, ejecutamos el método y guardamos el resultado
const resultado = metodoOriginal.apply(this, args);
this[cacheKey][key] = resultado;
return resultado;
};
return descriptor;
}
class Matematicas {
@Cachear
calcularFactorial(n: number): number {
console.log(`Calculando factorial de ${n}...`);
if (n <= 1) return 1;
return n * this.calcularFactorial(n - 1);
}
}
const math = new Matematicas();
console.log(math.calcularFactorial(5)); // Calcula y guarda en caché
console.log(math.calcularFactorial(5)); // Usa el resultado en caché
Decoradores para medición de rendimiento
Los decoradores son ideales para medir el tiempo de ejecución de métodos:
function MedirTiempo(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const metodoOriginal = descriptor.value;
descriptor.value = function(...args: any[]) {
const inicio = performance.now();
const resultado = metodoOriginal.apply(this, args);
const fin = performance.now();
const tiempo = fin - inicio;
console.log(`${propertyKey} tardó ${tiempo.toFixed(2)}ms en ejecutarse`);
return resultado;
};
return descriptor;
}
class Ordenador {
@MedirTiempo
ordenarArray(arr: number[]): number[] {
// Método de ordenación ineficiente para demostración
return [...arr].sort((a, b) => a - b);
}
}
const ordenador = new Ordenador();
const arrayGrande = Array.from({ length: 10000 }, () => Math.random() * 1000);
ordenador.ordenarArray(arrayGrande);
// Salida: ordenarArray tardó 15.23ms en ejecutarse
Combinando múltiples decoradores de método
Podemos aplicar varios decoradores a un mismo método para combinar funcionalidades:
function Registrar(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const metodoOriginal = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Llamando a ${propertyKey}`);
return metodoOriginal.apply(this, args);
};
return descriptor;
}
function ValidarNumeros(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const metodoOriginal = descriptor.value;
descriptor.value = function(...args: any[]) {
// Verificamos que todos los argumentos sean números
if (!args.every(arg => typeof arg === 'number')) {
throw new Error("Todos los argumentos deben ser números");
}
return metodoOriginal.apply(this, args);
};
return descriptor;
}
class Calculadora {
@Registrar
@ValidarNumeros
multiplicar(a: number, b: number): number {
return a * b;
}
}
const calc = new Calculadora();
console.log(calc.multiplicar(4, 5)); // 20
// Salida: Llamando a multiplicar
try {
console.log(calc.multiplicar(4, "5" as any));
} catch (error) {
console.error(error.message); // Todos los argumentos deben ser números
}
Decoradores para métodos asíncronos
Los decoradores también funcionan con métodos asíncronos, permitiéndonos manipular promesas:
function Reintentar(intentos: number, retraso: number = 1000) {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const metodoOriginal = descriptor.value;
descriptor.value = async function(...args: any[]) {
let ultimoError: Error;
for (let intento = 0; intento < intentos; intento++) {
try {
return await metodoOriginal.apply(this, args);
} catch (error) {
console.log(`Intento ${intento + 1} fallido: ${error.message}`);
ultimoError = error;
if (intento < intentos - 1) {
// Esperamos antes del siguiente intento
await new Promise(resolve => setTimeout(resolve, retraso));
}
}
}
throw new Error(`Fallaron todos los intentos: ${ultimoError.message}`);
};
return descriptor;
};
}
class ServicioAPI {
private intentos = 0;
@Reintentar(3, 500)
async obtenerDatos(id: number): Promise<any> {
this.intentos++;
// Simulamos un error en los primeros dos intentos
if (this.intentos < 3) {
throw new Error("Error de conexión");
}
return { id, datos: "Información importante" };
}
}
async function probar() {
const api = new ServicioAPI();
try {
const resultado = await api.obtenerDatos(123);
console.log("Éxito:", resultado);
} catch (error) {
console.error("Error final:", error.message);
}
}
probar();
// Salida:
// Intento 1 fallido: Error de conexión
// Intento 2 fallido: Error de conexión
// Éxito: { id: 123, datos: "Información importante" }
Los decoradores de método son una herramienta extremadamente versátil que nos permite implementar aspectos transversales como logging, validación, control de acceso y manejo de errores de manera declarativa, mejorando la legibilidad y mantenibilidad de nuestro código TypeScript.
Ejercicios de esta lección Decoradores básicos
Evalúa tus conocimientos de esta lección Decoradores básicos con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Funciones
Reto composición de funciones
Reto tipos especiales
Reto tipos genéricos
Módulos
Polimorfismo
Funciones TypeScript
Interfaces
Funciones puras
Reto namespaces
Funciones flecha
Polimorfismo
Operadores
Conversor de unidades
Funciones flecha
Control de flujo
Herencia
Clases
Proyecto validación de tipado
Clases y objetos
Encapsulación
Herencia
Proyecto sistema de votación
Reto genéricos con clases
Inmutabilidad
Interfaces
Funciones de alto orden
Reto map y filter
Control de flujo
Interfaces
Reto funciones orden superior
Herencia y clases abstractas
Reto tipos mapped
Herencia de clases
Reto funciones puras
Variables y constantes
Introducción a TypeScript
Reto testing unitario
Funciones de primera clase
Clases
OOP y CRUD en TypeScript
Interfaces y su implementación
Tipos genéricos
Namespaces
Proyecto calculadora gastos
Operadores y expresiones
Proyecto generador de contraseñas
Reto unión e intersección
Encapsulación
Tipos de unión e intersección
Tipos de unión e intersección
Reto hola mundo en TS
Variables y constantes
Funciones puras
Control de flujo
Introducción a TypeScript
Resolución de módulos
Control de flujo
Reto tipos de utilidad
Reto tipos literales y condicionales
Reto exportar e importar
Propiedades y métodos
Tipos de utilidad
Clases y objetos
Tipos de datos, variables y constantes
Proyecto Minigestor de tareas
Operadores
Funciones flecha y contexto
Funciones
Reto type aliases
Funciones de alto orden
Funciones y parámetros tipados
Tipos literales
Reto enums
Tipos de utilidad
Modificadores de acceso y encapsulación
Polimorfismo
Tipos genéricos
Reto módulos
Tipos literales
Inmutabilidad
Proyecto Generator de datos
Variables y constantes
Funciones de primera clase
Todas las lecciones de TypeScript
Accede a todas las lecciones de TypeScript y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Typescript
Introducción Y Entorno
Instalación Y Configuración De Typescript
Introducción Y Entorno
Tipos De Datos, Variables Y Constantes
Sintaxis
Operadores Y Expresiones
Sintaxis
Control De Flujo
Sintaxis
Funciones Y Parámetros Tipados
Sintaxis
Funciones Flecha Y Contexto
Sintaxis
Enums
Sintaxis
Type Aliases Y Aserciones De Tipo
Sintaxis
Clases Y Objetos
Programación Orientada A Objetos
Interfaces Y Su Implementación
Programación Orientada A Objetos
Modificadores De Acceso Y Encapsulación
Programación Orientada A Objetos
Herencia Y Clases Abstractas
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Decoradores Básicos
Programación Orientada A Objetos
Propiedades Y Métodos
Programación Orientada A Objetos
Inmutabilidad
Programación Funcional
Funciones Puras
Programación Funcional
Funciones De Primera Clase
Programación Funcional
Funciones De Alto Orden
Programación Funcional
Conceptos Básicos E Inmutabilidad
Programación Funcional
Funciones De Primera Clase Y Orden Superior
Programación Funcional
Composición De Funciones
Programación Funcional
Métodos Funcionales De Arrays (Map, Filter, Reduce)
Programación Funcional
Tipos Literales
Tipos Intermedios Y Avanzados
Tipos Genéricos
Tipos Intermedios Y Avanzados
Tipos De Unión E Intersección
Tipos Intermedios Y Avanzados
Tipos De Utilidad
Tipos Intermedios Y Avanzados
Unknown, Never Y Tipos Especiales
Tipos Intermedios Y Avanzados
Tipos Mapped
Tipos Intermedios Y Avanzados
Genéricos Con Clases E Interfaces
Tipos Intermedios Y Avanzados
Módulos
Namespaces Y Módulos
Namespaces
Namespaces Y Módulos
Resolución De Módulos
Namespaces Y Módulos
Exportación E Importación De Módulos
Namespaces Y Módulos
Introducción A Módulos
Namespaces Y Módulos
Testing Unitario En Typescript
Testing
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender qué son los decoradores y su sintaxis básica en TypeScript.
- Identificar los diferentes tipos de decoradores: clase, método, propiedad, parámetro y accesor.
- Aprender a crear decoradores simples y decoradores de fábrica con parámetros.
- Aplicar decoradores para casos prácticos como validación, registro, inyección de dependencias y control de acceso.
- Entender cómo combinar múltiples decoradores y las limitaciones de su uso en TypeScript.