Decoradores de TypeScript
Los decoradores son una característica experimental de TypeScript.
Permiten a los desarrolladores agregar funcionalidad adicional a clases, métodos, propiedades o parámetros sin modificar la clase original.
Un decorador es esencialmente una función que puede ser invocada en tiempo de ejecución para modificar el comportamiento del objeto objetivo.
Diagrama SVG: Tipos de decoradoresIntroducción a los decoradores
Los decoradores utilizan el símbolo `@` como azúcar sintáctico y pueden aplicarse a clases, métodos, accesores, propiedades o parámetros.
Este patrón se usa comúnmente en el desarrollo de frameworks, como Angular, TypeORM, etc., que utilizan ampliamente decoradores para implementar inyección de dependencias, validación de datos y otras funcionalidades.
Nota:Los decoradores son actualmente una característica experimental y deben habilitarse explícitamente en tsconfig.json. Al usarlos en producción, asegúrese de verificar el nivel de soporte del proyecto para características experimentales.
Configuración para habilitar decoradores
Antes de usar decoradores, debe habilitar las opciones de compilación correspondientes en el archivo de configuración de TypeScript tsconfig.json.
Configuración de tsconfig.json
"compilerOptions": {
// Habilitar la sintaxis de decoradores
"experimentalDecorators": true,
// Habilitar metadatos de decoradores (para frameworks de inyección de dependencias)
"emitDecoratorMetadata": true
}
}
Descripción de parámetros:
- experimentalDecorators:Habilita la compatibilidad con la sintaxis de decoradores; este es el requisito previo para usar decoradores.
- emitDecoratorMetadata:Genera metadatos de decoradores en el JavaScript compilado, para que los use el framework de inyección de dependencias.
Decorador de clase
El decorador de clase se aplica al constructor de la clase y puede modificar la definición de la clase o agregar lógica de procesamiento adicional.
El decorador de clase recibe un parámetro: el constructor de la clase objetivo.
Uso básico del decorador de clase
// El parámetro target es el constructor de la clase decorada
function sealed(target: Function) {
// Imprimir el nombre de la clase a la que se aplica el decorador
console.log("Decorador aplicado a: " + target.name);
// Usar Object.seal para sellar el constructor y el prototipo
// Evitar agregar o eliminar propiedades en tiempo de ejecución
Object.seal(target);
Object.seal(target.prototype);
}
// Aplicar el decorador a la clase usando la sintaxis @
@sealed
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
// Crear una instancia para probar
var person = new Person("EJEMPLO");
console.log("Creado: " + person.name);
// Intentar agregar una nueva propiedad (será bloqueado porque la clase está sellada)
// person.age = 25; // falla silenciosamente
Resultado de ejecución:
装饰器 applied to: Person 创建: EJEMPLO
Explicación:
El decorador de clase se ejecuta en el momento de la definición de la clase y se usa generalmente para modificar el comportamiento de la clase, agregar metadatos o implementar AOP (Programación Orientada a Aspectos).
Decorador de método
El decorador de método se aplica a los métodos de una clase y puede modificar el descriptor de propiedades (Property Descriptor) del método.
El decorador de método recibe tres parámetros: el objeto objetivo, el nombre de la propiedad y el descriptor de propiedades.
Uso básico del decorador de método
// Devuelve una función decoradora
function enumerable(value: boolean) {
// Devuelve una función decoradora que recibe tres parámetros
return function (
target: any, // El objeto prototipo de la clase a la que pertenece
propertyKey: string, // Nombre del método
descriptor: PropertyDescriptor // Descriptor de propiedades
) {
// Modificar la característica enumerable de la propiedad
// false indica que el método no es enumerable (no se puede recorrer)
descriptor.enumerable = value;
};
}
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
// Aplicar el decorador para establecer que el método no sea enumerable
@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}
}
var g = new Greeter("World");
// Verificar si el método greet es enumerable
console.log("Método enumerable: " + g.propertyIsEnumerable("greet"));
// Recorrer las propiedades del objeto
for (var key in g) {
console.log("Propiedades: " + key);
}
Resultado de ejecución:
方法可枚举: false
Consejo:PropertyDescriptor contiene propiedades como enumerable, configurable, writable y value, que pueden modificarse según sea necesario.
Decorador de accessor
El decorador de accessor se aplica a los métodos getter y setter de una clase.
Al igual que el decorador de método, el decorador de accessor también puede modificar el descriptor de propiedades.
Uso del decorador de accessor
function configurable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
// Modificar la característica configurable de la propiedad
// false indica que el accessor no puede reconfigurarse ni eliminarse
descriptor.configurable = value;
};
}
class Point {
private _x: number = 0;
private _y: number = 0;
// Usar el decorador para bloquear el getter
@configurable(false)
get x() {
return this._x;
}
@configurable(false)
get y() {
return this._y;
}
set x(value: number) {
this._x = value;
}
set y(value: number) {
this._y = value;
}
}
var point = new Point();
point.x = 10;
point.y = 20;
console.log("Coordenadas: (" + point.x + ", " + point.y + ")");
Explicación:
El decorador de accessor no puede aplicarse simultáneamente al getter y al setter de la misma propiedad; solo se puede elegir uno de ellos.
Decorador de propiedad
El decorador de propiedad se aplica a las definiciones de propiedades de una clase.
El decorador de propiedad recibe dos parámetros: el objeto objetivo y el nombre de la propiedad.
Uso del decorador de propiedad
function format(formatString: string) {
return function (
target: any, // Objeto prototipo de la clase
propertyKey: string // Nombre de la propiedad
) {
// Almacenar metadatos en el objeto objetivo
// Usar propertyKey + "_format" como nombre de clave para evitar conflictos
Object.defineProperty(target, propertyKey + "_format", {
value: formatString,
writable: false,
enumerable: false,
configurable: true
});
};
}
class User {
// Aplicar el decorador de propiedad para especificar el formato de fecha
@format("YYYY-MM-DD")
birthDate: string;
constructor(birthDate: string) {
this.birthDate = birthDate;
}
}
var user = new User("1990-01-01");
console.log("Fecha de nacimiento: " + user.birthDate);
// Acceder a los metadatos almacenados
console.log("Formato de fecha: " + (user as any).birthDate_format);
Resultado de ejecución:
出生日期: 1990-01-01 日期格式: YYYY-MM-DD
Decorador de parámetros
El decorador de parámetros se aplica a los parámetros de los métodos de una clase y puede agregar metadatos o marcadores a los parámetros.
El decorador de parámetros recibe tres parámetros: el objeto objetivo, el nombre del método y el índice del parámetro dentro de la función.
Uso del decorador de parámetros
// Se usa para registrar información de parámetros o realizar validaciones
function logParameter(
target: any, // Objeto prototipo de la clase
propertyKey: string, // Nombre del método
parameterIndex: number // Posición del índice del parámetro en la función (comenzando desde 0)
) {
console.log("Decorador de parámetros: " + propertyKey +
" nº " + (parameterIndex + 1) + " de parámetro");
}
class Greeter {
greeting: string;
constructor(greeting: string) {
this.greeting = greeting;
}
// Aplicar el decorador usando la sintaxis @ antes del parámetro
greet(@logParameter name: string) {
return this.greeting + ", " + name;
}
}
var greeter = new Greeter("Hello");
greeter.greet("EJEMPLO");
Resultado de ejecución:
参数装饰器: greet 第 1 个参数
Fábrica de decoradores
Una fábrica de decoradores es una función que devuelve una función decoradora.
Mediante la fábrica de decoradores, se pueden pasar parámetros personalizados al aplicar el decorador, lo que permite una configuración más flexible.
Implementación de registros con color mediante fábrica de decoradores
function color(colorCode: string) {
// colorCode es el código de color de la secuencia de escape ANSI
// Por ejemplo: 34 = azul, 31 = rojo, 32 = verde
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
// Guardar el método original
var originalMethod = descriptor.value;
// Reescribir el método para agregar color
descriptor.value = function (...args: any[]) {
// Llamar al método original para obtener el valor de retorno
var result = originalMethod.apply(this, args);
// Si está en un entorno de terminal, agregar color a la salida
// Formato de secuencia de escape ANSI: \x1b[código de color m contenido \x1b[0m
return "\x1b[" + colorCode + "m" + result + "\x1b[0m";
};
};
}
class Logger {
// Usar una fábrica de decoradores, pasando el código azul 34
@color("34")
log(message: string): string {
return message;
}
@color("31")
error(message: string): string {
return message;
}
@color("32")
success(message: string): string {
return message;
}
}
var logger = new Logger();
console.log(logger.log("Este es un registro azul"));
console.log(logger.error("Este es un error rojo"));
console.log(logger.success("Este es un éxito verde"));
Resultado de la ejecución:
Esto es un registro azul (se muestra en azul en la terminal) Esto es un error rojo (se muestra en rojo en la terminal) Esto es un éxito verde (se muestra en verde en la terminal)
Explicación:La fábrica de decoradores es la forma más común en el desarrollo real; permite pasar parámetros al aplicar decoradores, logrando configuración.
Orden de ejecución de los decoradores
Cuando una clase tiene múltiples decoradores, el orden de ejecución sigue reglas específicas.
- Los decoradores se aplican de abajo hacia arriba.
- Múltiples decoradores del mismo tipo se ejecutan de derecha a izquierda.
- Los decoradores de parámetros se ejecutan antes que los decoradores de métodos.
Ejemplo del orden de ejecución de los decoradores
function first() {
console.log("first decorador");
return function (target: any) {
console.log("first función decoradora");
};
}
function second() {
console.log("second decorador");
return function (target: any) {
console.log("second función decoradora");
};
}
@first()
@second()
class MyClass {
name: string;
}
var obj = new MyClass();
Resultado de la ejecución:
second 装饰器 first 装饰器 second 装饰器函数 first 装饰器函数
Explicación:
La función del decorador primero ejecuta la definición (console.log) y luego ejecuta las funciones decoradoras en orden de abajo hacia arriba.
Escenarios de aplicación práctica
Los decoradores tienen amplios escenarios de aplicación en proyectos reales.
Registro de logs
Registrar automáticamente los logs de llamadas a métodos.
Ejemplo
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
var originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log("Llamar al método: " + propertyKey + ", parámetros: " + JSON.stringify(args));
var result = originalMethod.apply(this, args);
console.log("El método devuelve: " + JSON.stringify(result));
return result;
};
}
class MathService {
@log
add(a: number, b: number): number {
return a + b;
}
@log
multiply(a: number, b: number): number {
return a * b;
}
}
var math = new MathService();
console.log("Resultado del cálculo: " + math.add(5, 3));
Resultado de la ejecución:
调用方法: add,参数: [5,3] 方法返回: 8 计算结果: 8
Verificación de permisos
Implementar comprobación de permisos a nivel de método.
Ejemplo
var currentUser = { role: "admin" };
// Decorador de permisos
function requireRole(role: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
var originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
if (currentUser.role !== role) {
console.log("Permisos insuficientes, no se puede ejecutar " + propertyKey);
return null;
}
return originalMethod.apply(this, args);
};
};
}
class AdminService {
@requireRole("admin")
deleteUser(id: number): string {
return "Eliminar usuario " + id + " exitoso";
}
@requireRole("admin")
viewUser(id: number): string {
return "Ver usuario " + id;
}
}
var admin = new AdminService();
console.log(admin.viewUser(1));
console.log(admin.deleteUser(1));
// Simular un usuario normal
currentUser = { role: "user" };
console.log(admin.deleteUser(2));
Resultado de la ejecución:
查看用户 1 删除用户 1 成功 权限不足,无法执行 deleteUser
Notas
- Característica experimental:Los decoradores son una propuesta _stage 3_ de ECMAScript y actualmente siguen siendo una característica experimental.
- Opciones de compilación:Debe habilitarse experimentalDecorators
- Definición de tipos:Se necesita una versión reciente de TypeScript para obtener soporte completo de tipos.
- Nota de depuración:Los decoradores se ejecutan en tiempo de compilación; algunas herramientas de depuración pueden no mapear correctamente las ubicaciones del código fuente.
Recomendación:Al usar decoradores en un proyecto, se recomienda crear clases de utilidad o bibliotecas de funciones dedicadas para gestionar de forma unificada la definición y el uso de los decoradores.
Resumen
Los decoradores de TypeScript proporcionan una potente capacidad de metaprogramación.
- Decorador de clase:Modifica la clase en sí; se puede usar para añadir metadatos, bloquear clases, etc.
- Decorador de método:Modifica atributos del método; se puede usar para logs, validaciones, etc.
- Decorador de acceso (accessor):Modifica getters/setters y controla la configurabilidad de las propiedades.
- Decorador de propiedad:Añadir metadatos a la propiedad.
- Decorador de parámetro:Marcar o validar parámetros de métodos.
- Fábrica de decoradores:Lograr una configuración de decoradores más flexible mediante parametrización.
Otras extensiones