Els decoradors són una característica avançada de TypeScript que permeten modificar el comportament de classes, mètodes, propietats i paràmetres. Són una forma de metaprogramació que permet afegir funcionalitats addicionals de manera declarativa.

Què són els Decoradors?

Els decoradors són funcions que s'apliquen a una classe, mètode, propietat o paràmetre per modificar el seu comportament. S'utilitzen principalment en frameworks com Angular per afegir metadades a les classes i els seus membres.

Tipus de Decoradors

  1. Decoradors de Classe: S'apliquen a la definició d'una classe.
  2. Decoradors de Mètode: S'apliquen a un mètode d'una classe.
  3. Decoradors de Propietat: S'apliquen a una propietat d'una classe.
  4. Decoradors de Paràmetre: S'apliquen a un paràmetre d'un mètode d'una classe.

Exemple de Decorador de Classe

Un decorador de classe és una funció que pren com a argument el constructor de la classe i pot modificar-lo o retornar un nou constructor.

function logClass(target: Function) {
    console.log(`Class ${target.name} is created`);
}

@logClass
class MyClass {
    constructor() {
        console.log('MyClass instance created');
    }
}

const myClassInstance = new MyClass();

Explicació

  1. Definició del Decorador: logClass és una funció que pren el constructor de la classe com a argument.
  2. Aplicació del Decorador: @logClass s'aplica a la classe MyClass.
  3. Efecte del Decorador: Quan es crea una instància de MyClass, es registra un missatge a la consola.

Exemple de Decorador de Mètode

Un decorador de mètode és una funció que pren com a arguments el prototip de la classe, el nom del mètode i la descripció de la propietat.

function logMethod(target: any, propertyName: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = function (...args: any[]) {
        console.log(`Method ${propertyName} is called with arguments: ${args}`);
        return originalMethod.apply(this, args);
    };

    return descriptor;
}

class MyClass {
    @logMethod
    myMethod(arg1: number, arg2: string) {
        console.log('Executing myMethod');
    }
}

const myClassInstance = new MyClass();
myClassInstance.myMethod(42, 'hello');

Explicació

  1. Definició del Decorador: logMethod és una funció que modifica el descriptor del mètode.
  2. Aplicació del Decorador: @logMethod s'aplica al mètode myMethod.
  3. Efecte del Decorador: Quan es crida myMethod, es registra un missatge a la consola amb els arguments passats.

Exemple de Decorador de Propietat

Un decorador de propietat és una funció que pren com a arguments el prototip de la classe i el nom de la propietat.

function logProperty(target: any, propertyName: string) {
    let value = target[propertyName];

    const getter = () => {
        console.log(`Get value of ${propertyName}: ${value}`);
        return value;
    };

    const setter = (newValue: any) => {
        console.log(`Set value of ${propertyName} to ${newValue}`);
        value = newValue;
    };

    Object.defineProperty(target, propertyName, {
        get: getter,
        set: setter,
        enumerable: true,
        configurable: true
    });
}

class MyClass {
    @logProperty
    myProperty: string;

    constructor() {
        this.myProperty = 'initial value';
    }
}

const myClassInstance = new MyClass();
myClassInstance.myProperty = 'new value';
console.log(myClassInstance.myProperty);

Explicació

  1. Definició del Decorador: logProperty és una funció que defineix getters i setters per a la propietat.
  2. Aplicació del Decorador: @logProperty s'aplica a la propietat myProperty.
  3. Efecte del Decorador: Quan es llegeix o s'escriu myProperty, es registra un missatge a la consola.

Exemple de Decorador de Paràmetre

Un decorador de paràmetre és una funció que pren com a arguments el prototip de la classe, el nom del mètode i la posició del paràmetre.

function logParameter(target: any, propertyName: string, parameterIndex: number) {
    const metadataKey = `log_${propertyName}_parameters`;
    if (Array.isArray(target[metadataKey])) {
        target[metadataKey].push(parameterIndex);
    } else {
        target[metadataKey] = [parameterIndex];
    }
}

class MyClass {
    myMethod(@logParameter param1: number, param2: string) {
        console.log('Executing myMethod');
    }
}

const myClassInstance = new MyClass();
myClassInstance.myMethod(42, 'hello');

Explicació

  1. Definició del Decorador: logParameter és una funció que guarda la posició del paràmetre decorat.
  2. Aplicació del Decorador: @logParameter s'aplica al paràmetre param1 de myMethod.
  3. Efecte del Decorador: La posició del paràmetre decorat es guarda en una metadada.

Exercicis Pràctics

Exercici 1: Decorador de Classe

Crea un decorador de classe que afegeixi una propietat createdAt a la classe, que emmagatzemi la data de creació de la instància.

function addCreatedAt(target: Function) {
    target.prototype.createdAt = new Date();
}

@addCreatedAt
class MyClass {
    constructor() {
        console.log('MyClass instance created');
    }
}

const myClassInstance = new MyClass();
console.log(myClassInstance.createdAt);

Exercici 2: Decorador de Mètode

Crea un decorador de mètode que mesuri el temps d'execució del mètode i el registri a la consola.

function measureExecutionTime(target: any, propertyName: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = function (...args: any[]) {
        const start = performance.now();
        const result = originalMethod.apply(this, args);
        const end = performance.now();
        console.log(`Execution time of ${propertyName}: ${end - start}ms`);
        return result;
    };

    return descriptor;
}

class MyClass {
    @measureExecutionTime
    myMethod() {
        for (let i = 0; i < 1000000; i++) {} // Simulació de treball
    }
}

const myClassInstance = new MyClass();
myClassInstance.myMethod();

Exercici 3: Decorador de Propietat

Crea un decorador de propietat que faci que la propietat sigui de només lectura.

function readonly(target: any, propertyName: string) {
    Object.defineProperty(target, propertyName, {
        writable: false
    });
}

class MyClass {
    @readonly
    myProperty: string = 'initial value';
}

const myClassInstance = new MyClass();
myClassInstance.myProperty = 'new value'; // Això hauria de fallar
console.log(myClassInstance.myProperty);

Solucions

Solució Exercici 1

function addCreatedAt(target: Function) {
    target.prototype.createdAt = new Date();
}

@addCreatedAt
class MyClass {
    constructor() {
        console.log('MyClass instance created');
    }
}

const myClassInstance = new MyClass();
console.log(myClassInstance.createdAt);

Solució Exercici 2

function measureExecutionTime(target: any, propertyName: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = function (...args: any[]) {
        const start = performance.now();
        const result = originalMethod.apply(this, args);
        const end = performance.now();
        console.log(`Execution time of ${propertyName}: ${end - start}ms`);
        return result;
    };

    return descriptor;
}

class MyClass {
    @measureExecutionTime
    myMethod() {
        for (let i = 0; i < 1000000; i++) {} // Simulació de treball
    }
}

const myClassInstance = new MyClass();
myClassInstance.myMethod();

Solució Exercici 3

function readonly(target: any, propertyName: string) {
    Object.defineProperty(target, propertyName, {
        writable: false
    });
}

class MyClass {
    @readonly
    myProperty: string = 'initial value';
}

const myClassInstance = new MyClass();
myClassInstance.myProperty = 'new value'; // Això hauria de fallar
console.log(myClassInstance.myProperty);

Conclusió

Els decoradors són una eina poderosa en TypeScript que permeten modificar el comportament de classes, mètodes, propietats i paràmetres de manera declarativa. Són especialment útils en el desenvolupament de frameworks i biblioteques, on es necessita afegir metadades o modificar el comportament de les entitats de manera consistent. Amb els exemples i exercicis proporcionats, hauríeu de tenir una bona comprensió de com crear i utilitzar decoradors en els vostres projectes TypeScript.

© Copyright 2024. Tots els drets reservats