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 decoradores Fondo Título Tipos de decoradores y ubicación de aplicación Centro: Clase @ClassDecorator Decorador de clase Flechas hacia varias direcciones Arriba: Decorador de propiedad Decorador de propiedad @propertyDecorator Derecha: Decorador de método Decorador de método @methodDecorator Abajo: Decorador de parámetros Decorador de parámetros @paramDecorator Abajo a la derecha: Decorador de accessor Decorador de accessor @getterDecorator Explicación de la fábrica de decoradores Fábrica de decoradores (Decorator Factory) Una fábrica de decoradores es una función que devuelve una función decoradora, lo que permite la configuración mediante parámetros. Por ejemplo: function color(code: string) { return function(target) { ... }; } → @color("34") Marcador de flecha

Introducció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

// Definir una función decoradora 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

// Definir una fábrica de decoradores 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

// Fábrica de decoradores 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

// Fábrica de decoradores 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

// 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

// Fábrica de decoradores: recibe parámetros de configuración y devuelve una función decoradora
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

// Uso de múltiples decoradores superpuestos
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

// Decorador de registro
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

// Simular el rol del usuario actual
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