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 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.
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:
target
: El prototipo de la clasepropertyKey
: El nombre del métododescriptor
: 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.
Otros ejercicios de programación de TypeScript
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
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
Proyecto Inventario de productos
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 Y Efectos Secundarios
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 Y Tipos Condicionales
Tipos Intermedios Y Avanzados
Tipos Genéricos Básicos
Tipos Intermedios Y Avanzados
Tipos De Unión E Intersección
Tipos Intermedios Y Avanzados
Tipos De Utilidad (Partial, Required, Pick, Etc)
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
- 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.