Decoradores básicos

Intermedio
TypeScript
TypeScript
Actualizado: 09/05/2025

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

¿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 proporcionan una forma elegante y declarativa de añadir funcionalidades adicionales a elementos existentes.

Un decorador es esencialmente una función especial que se aplica a una declaración mediante la sintaxis @nombreDecorador. Esta sintaxis declarativa hace que el código sea más legible y expresivo, facilitando la comprensión de las capacidades adicionales.

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:

// Ejemplo básico de un decorador
function miDecorador(target: any) {
  console.log("El decorador se ha ejecutado");
}

// Aplicando el decorador a una clase
@miDecorador
class Ejemplo {
  // Contenido de la clase
}

Los decoradores se ejecutan durante la definición de la clase, no cuando se crea una instancia, lo que los hace ideales para añadir metadatos o realizar transformaciones en tiempo de compilación.

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.

¿Te está gustando esta lección?

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

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 EstamparFecha(constructor: Function) {
  // Añadimos una propiedad a la clase
  constructor.prototype.fechaCreacion = new Date().toISOString();
  
  // Añadimos un método a la clase
  constructor.prototype.mostrarFecha = function() {
    console.log(`Clase creada el: ${this.fechaCreacion}`);
  };
}

// Aplicamos el decorador
@EstamparFecha
class Proyecto {
  nombre: string;
  
  constructor(nombre: string) {
    this.nombre = nombre;
  }
}

// Uso de la clase decorada
const proyecto = new Proyecto("Mi Proyecto");

// Accedemos a la propiedad y método añadidos (con casting)
console.log((proyecto as any).fechaCreacion);
(proyecto as any).mostrarFecha();

Este ejemplo muestra cómo un decorador puede añadir propiedades y métodos al prototipo de una clase existente, extendiendo su funcionalidad sin modificar su definición original.

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.

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 permiten modificar el comportamiento de un método específico. Reciben tres parámetros:

  1. target: El prototipo de la clase
  2. propertyKey: El nombre del método
  3. descriptor: El descriptor de propiedad del método

Estructura básica

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(`Llamada a ${propertyKey} con argumentos: ${JSON.stringify(args)}`);
    
    // Llamamos al método original y capturamos su resultado
    const resultado = metodoOriginal.apply(this, args);
    
    console.log(`${propertyKey} retornó: ${JSON.stringify(resultado)}`);
    return resultado;
  };
  
  return descriptor;
}

class Calculadora {
  @Registrar
  sumar(a: number, b: number): number {
    return a + b;
  }
}

const calc = new Calculadora();
calc.sumar(5, 3);
// Salida:
// Llamada a sumar con argumentos: [5,3]
// sumar retornó: 8

Este decorador de método añade capacidades de registro (logging) al método sumar, mostrando información antes y después de su ejecución, sin modificar la lógica del propio método.

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

Relación con clases y métodos estudiados

Los decoradores complementan las clases y métodos que hemos estudiado anteriormente en el curso:

  • Los decoradores operan sobre las clases que aprendimos a crear, permitiéndonos extender su funcionalidad
  • Pueden modificar métodos sin alterar su implementación original
  • Respetan los modificadores de acceso (public, private, protected)
  • Funcionan tanto con métodos de instancia como con métodos estáticos

Esta característica proporciona una capa adicional de abstracción y reutilización que se integra perfectamente con el paradigma orientado a objetos.

Parámetros de decoradores y aplicaciones prácticas

Decoradores con parámetros (decoradores de fábrica)

Para crear decoradores más flexibles que acepten configuraciones, usamos "decoradores de fábrica". Estos son funciones que retornan el decorador real:

// Decorador de fábrica que acepta parámetros
function RepetirVeces(veces: number) {
  // Esta función es el decorador real que se aplicará
  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);
      }
      
      return resultado;
    };
    
    return descriptor;
  };
}

class Notificador {
  @RepetirVeces(3)
  enviarMensaje(mensaje: string): void {
    console.log(`Enviando: ${mensaje}`);
  }
}

const notif = new Notificador();
notif.enviarMensaje("¡Alerta importante!");
// Salida:
// Enviando: ¡Alerta importante!
// Enviando: ¡Alerta importante!
// Enviando: ¡Alerta importante!

Este patrón nos permite parametrizar nuestros decoradores, haciéndolos mucho más versátiles.

Ejemplo práctico: Validación de datos

Los decoradores son extremadamente útiles para implementar validaciones declarativas:

function ValidarNumeroPositivo(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const metodoOriginal = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    // Validamos que todos los argumentos sean números positivos
    for (const arg of args) {
      if (typeof arg !== 'number' || arg <= 0) {
        throw new Error(`El método ${propertyKey} requiere números positivos`);
      }
    }
    
    return metodoOriginal.apply(this, args);
  };
  
  return descriptor;
}

class Rectangulo {
  @ValidarNumeroPositivo
  calcularArea(ancho: number, alto: number): number {
    return ancho * alto;
  }
}

const rect = new Rectangulo();
console.log(rect.calcularArea(5, 3)); // 15

try {
  rect.calcularArea(-2, 4); // Error
} catch (error) {
  console.error(error.message); // El método calcularArea requiere números positivos
}

Esta validación asegura que los argumentos cumplan ciertos criterios antes de ejecutar la lógica del método, mejorando la robustez del código.

Ejemplo práctico: Control de acceso

Los decoradores pueden implementar control de acceso o autenticación:

function SoloAdministrador(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const metodoOriginal = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    // Verificamos si el usuario tiene el rol necesario
    if (!(this as any).usuario?.esAdmin) {
      throw new Error("Acceso denegado: Se requieren permisos de administrador");
    }
    
    return metodoOriginal.apply(this, args);
  };
  
  return descriptor;
}

class Panel {
  usuario: { nombre: string; esAdmin: boolean };
  
  constructor(usuario: { nombre: string; esAdmin: boolean }) {
    this.usuario = usuario;
  }
  
  @SoloAdministrador
  configurarSistema(): void {
    console.log("Sistema configurado correctamente");
  }
}

const admin = new Panel({ nombre: "Admin", esAdmin: true });
const usuario = new Panel({ nombre: "Usuario", esAdmin: false });

admin.configurarSistema(); // Sistema configurado correctamente

try {
  usuario.configurarSistema(); // Error
} catch (error) {
  console.error(error.message); // Acceso denegado: Se requieren permisos de administrador
}

Este ejemplo muestra cómo un decorador puede implementar lógica de control de acceso antes de permitir la ejecución de un método.

Combinando decoradores

Es posible aplicar múltiples decoradores a un mismo elemento. Se evalúan de arriba hacia abajo, pero se aplican de abajo hacia arriba:

function Decorador1(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  console.log("Aplicando Decorador1");
  const metodoOriginal = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    console.log("Inicio Decorador1");
    const resultado = metodoOriginal.apply(this, args);
    console.log("Fin Decorador1");
    return resultado;
  };
  
  return descriptor;
}

function Decorador2(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  console.log("Aplicando Decorador2");
  const metodoOriginal = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    console.log("Inicio Decorador2");
    const resultado = metodoOriginal.apply(this, args);
    console.log("Fin Decorador2");
    return resultado;
  };
  
  return descriptor;
}

class Ejemplo {
  @Decorador1
  @Decorador2
  metodo() {
    console.log("Ejecutando método");
    return "resultado";
  }
}

const ej = new Ejemplo();
ej.metodo();
// Salida:
// Aplicando Decorador2
// Aplicando Decorador1
// Inicio Decorador1
// Inicio Decorador2
// Ejecutando método
// Fin Decorador2
// Fin Decorador1

La combinación de decoradores permite crear capas de funcionalidad que se complementan entre sí.

Conclusión

Los decoradores proporcionan una forma elegante y declarativa de extender la funcionalidad de nuestras clases y métodos en TypeScript. Aunque son una característica experimental, su uso está muy extendido en frameworks modernos.

El poder de los decoradores radica en su capacidad para encapsular aspectos transversales (como logging, validación, control de acceso) fuera de la lógica principal del código, mejorando así la legibilidad, mantenibilidad y reutilización.

Los decoradores representan una forma más avanzada de aplicar los principios de la programación orientada a objetos que hemos estudiado anteriormente, permitiéndonos crear abstracciones más potentes y expresivas.

En futuras lecciones, exploraremos decoradores más avanzados y su aplicación en patrones de diseño complejos.

Aprendizajes de esta lección

  • Entender qué son los decoradores y cómo se habilitan en TypeScript.
  • Implementar decoradores de clase para extender funcionalidades existentes.
  • Crear decoradores de método que modifiquen el comportamiento de funciones.
  • Diseñar decoradores con parámetros para personalizar su funcionamiento.
  • Aplicar decoradores en casos prácticos como validación y control de acceso.

Completa TypeScript y certifícate

Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.

Asistente IA

Resuelve dudas al instante

Ejercicios

Practica con proyectos reales

Certificados

Valida tus conocimientos

Más de 25.000 desarrolladores ya se han certificado con CertiDevs

⭐⭐⭐⭐⭐
4.9/5 valoración