TypeScript

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.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende TypeScript online

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

TypeScript
Test

Reto composición de funciones

TypeScript
Código

Reto tipos especiales

TypeScript
Código

Reto tipos genéricos

TypeScript
Código

Módulos

TypeScript
Test

Polimorfismo

TypeScript
Código

Funciones TypeScript

TypeScript
Código

Interfaces

TypeScript
Puzzle

Funciones puras

TypeScript
Puzzle

Reto namespaces

TypeScript
Código

Funciones flecha

TypeScript
Puzzle

Polimorfismo

TypeScript
Test

Operadores

TypeScript
Test

Conversor de unidades

TypeScript
Proyecto

Funciones flecha

TypeScript
Test

Control de flujo

TypeScript
Código

Herencia

TypeScript
Puzzle

Clases

TypeScript
Puzzle

Proyecto validación de tipado

TypeScript
Proyecto

Clases y objetos

TypeScript
Código

Encapsulación

TypeScript
Test

Herencia

TypeScript
Test

Proyecto sistema de votación

TypeScript
Proyecto

Reto genéricos con clases

TypeScript
Código

Inmutabilidad

TypeScript
Puzzle

Interfaces

TypeScript
Test

Funciones de alto orden

TypeScript
Test

Reto map y filter

TypeScript
Código

Control de flujo

TypeScript
Test

Interfaces

TypeScript
Código

Reto funciones orden superior

TypeScript
Código

Herencia y clases abstractas

TypeScript
Código

Reto tipos mapped

TypeScript
Código

Herencia de clases

TypeScript
Código

Reto funciones puras

TypeScript
Código

Variables y constantes

TypeScript
Puzzle

Introducción a TypeScript

TypeScript
Test

Reto testing unitario

TypeScript
Código

Funciones de primera clase

TypeScript
Puzzle

Clases

TypeScript
Test

OOP y CRUD en TypeScript

TypeScript
Proyecto

Interfaces y su implementación

TypeScript
Código

Tipos genéricos

TypeScript
Test

Namespaces

TypeScript
Test

Proyecto calculadora gastos

TypeScript
Proyecto

Operadores y expresiones

TypeScript
Código

Proyecto generador de contraseñas

TypeScript
Proyecto

Reto unión e intersección

TypeScript
Código

Encapsulación

TypeScript
Puzzle

Tipos de unión e intersección

TypeScript
Test

Tipos de unión e intersección

TypeScript
Puzzle

Reto hola mundo en TS

TypeScript
Código

Variables y constantes

TypeScript
Código

Funciones puras

TypeScript
Test

Control de flujo

TypeScript
Código

Introducción a TypeScript

TypeScript
Código

Resolución de módulos

TypeScript
Test

Control de flujo

TypeScript
Puzzle

Reto tipos de utilidad

TypeScript
Código

Reto tipos literales y condicionales

TypeScript
Código

Reto exportar e importar

TypeScript
Código

Propiedades y métodos

TypeScript
Código

Tipos de utilidad

TypeScript
Test

Clases y objetos

TypeScript
Código

Tipos de datos, variables y constantes

TypeScript
Código

Proyecto Minigestor de tareas

TypeScript
Proyecto

Operadores

TypeScript
Puzzle

Funciones flecha y contexto

TypeScript
Código

Funciones

TypeScript
Puzzle

Reto type aliases

TypeScript
Código

Funciones de alto orden

TypeScript
Puzzle

Funciones y parámetros tipados

TypeScript
Código

Tipos literales

TypeScript
Puzzle

Reto enums

TypeScript
Código

Tipos de utilidad

TypeScript
Puzzle

Modificadores de acceso y encapsulación

TypeScript
Código

Polimorfismo

TypeScript
Puzzle

Tipos genéricos

TypeScript
Puzzle

Reto módulos

TypeScript
Código

Tipos literales

TypeScript
Test

Inmutabilidad

TypeScript
Test

Proyecto Generator de datos

TypeScript
Proyecto

Variables y constantes

TypeScript
Test

Funciones de primera clase

TypeScript
Test

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

TypeScript

Introducción Y Entorno

Instalación Y Configuración De Typescript

TypeScript

Introducción Y Entorno

Tipos De Datos, Variables Y Constantes

TypeScript

Sintaxis

Operadores Y Expresiones

TypeScript

Sintaxis

Control De Flujo

TypeScript

Sintaxis

Funciones Y Parámetros Tipados

TypeScript

Sintaxis

Funciones Flecha Y Contexto

TypeScript

Sintaxis

Enums

TypeScript

Sintaxis

Type Aliases Y Aserciones De Tipo

TypeScript

Sintaxis

Clases Y Objetos

TypeScript

Programación Orientada A Objetos

Interfaces Y Su Implementación

TypeScript

Programación Orientada A Objetos

Modificadores De Acceso Y Encapsulación

TypeScript

Programación Orientada A Objetos

Herencia Y Clases Abstractas

TypeScript

Programación Orientada A Objetos

Polimorfismo

TypeScript

Programación Orientada A Objetos

Decoradores Básicos

TypeScript

Programación Orientada A Objetos

Propiedades Y Métodos

TypeScript

Programación Orientada A Objetos

Inmutabilidad

TypeScript

Programación Funcional

Funciones Puras

TypeScript

Programación Funcional

Funciones De Primera Clase

TypeScript

Programación Funcional

Funciones De Alto Orden

TypeScript

Programación Funcional

Conceptos Básicos E Inmutabilidad

TypeScript

Programación Funcional

Funciones De Primera Clase Y Orden Superior

TypeScript

Programación Funcional

Composición De Funciones

TypeScript

Programación Funcional

Métodos Funcionales De Arrays (Map, Filter, Reduce)

TypeScript

Programación Funcional

Tipos Literales

TypeScript

Tipos Intermedios Y Avanzados

Tipos Genéricos

TypeScript

Tipos Intermedios Y Avanzados

Tipos De Unión E Intersección

TypeScript

Tipos Intermedios Y Avanzados

Tipos De Utilidad

TypeScript

Tipos Intermedios Y Avanzados

Unknown, Never Y Tipos Especiales

TypeScript

Tipos Intermedios Y Avanzados

Tipos Mapped

TypeScript

Tipos Intermedios Y Avanzados

Genéricos Con Clases E Interfaces

TypeScript

Tipos Intermedios Y Avanzados

Módulos

TypeScript

Namespaces Y Módulos

Namespaces

TypeScript

Namespaces Y Módulos

Resolución De Módulos

TypeScript

Namespaces Y Módulos

Exportación E Importación De Módulos

TypeScript

Namespaces Y Módulos

Introducción A Módulos

TypeScript

Namespaces Y Módulos

Testing Unitario En Typescript

TypeScript

Testing

Accede GRATIS a TypeScript y certifícate

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.