TypeScript – dekoratory [decorators]

TypeScript wspiera mechanizm zbliżony do adnotacji w języku Java, który nazywa się dekoratorami (ang. decorators). Pozwala on na dodawanie adnotacji oraz meta-programowanie.

Poprzez adnotowanie rozumie się oznaczanie pól/metod/klas tak aby później jakiś framework lub inne narzędzie mogło na podstawie tych oznaczeń coś zrobić. Dobrym przykładem jest tutaj oznaczanie pól do walidacji jak to wygląda przykładowo tutaj https://github.com/typestack/class-validator.

Meta-programowaniem zaś będzie wszelkiego rodzaju modyfikacją istniejącego kodu za pomocą dekoratorów (nadpisywanie konstruktora czy nadpisywanie metody).

Uruchomienie wsparcia dla dekoratorów

Dekoratory są eksperymentalnym mechanizmem w TypeScript (a przynajmniej w momencie pisania tego wpisu), stąd konieczne jest jawne zaznaczenie, że chcemy z niego korzystać. W tym celu należy dodać odpowiednią opcję w pliku tsconfig.json:

"compilerOptions": {
     "experimentalDecorators": true
}

Prosty dekorator dla klasy

function ClassDecorator(constructorFunction: Function){
    console.log(`I'm a decorator`);
}

@ClassDecorator
class Car {
    name: string;
    constructor(name: string){
        this.name = name;
    }
}


Jak widać, dekorator to funkcja, która przyjmuje odpowiednie argumenty. W przypadku dekoratora klas wystarczający jest jeden argument – funkcja służąca do konstrukcji obiektów (constructor function).

Dekorator dla pola klasy

Aby stworzyć dekorator dla pól klasy należy przygotować funkcję przyjmującą dwa argumenty:
1. Pierwszy argument to funkcja służąca jako konstruktor (constructor function). Czyli konstruktor klasy w której leży dekorowane pole.
2. Drugi argument to po prostu nawa pola

function PropertyDecorator(target: any, propertyName: string){
    console.log('target', target);
    console.log('propertyName', propertyName);
}

class Car {
    @PropertyDecorator
    name: string;
}

Dekorator dla metody klasy

Aby stworzyć dekorator dla metod klasy należy przygotować funkcję przyjmującą trzy argumenty:
1. Pierwszy argument to funkcja służąca jako konstruktor (constructor function). Czyli konstruktor klasy w której leży dekorowane pole.
2. Drugi argument to po prostu nawa pola
3. Deskryptor metody – obiekt posiadający pola:
configurable – mówi o tym czy użytkownik może modyfikować pola deskryptora
enumerable – mówi o tym czy obiekt jest przystosowany do wykorzystania w pętli
value – tutaj znajduje się metoda
writable – mówi o tym czy użytkownik może nadpisać tą metodę

function FunctionDecorator(target: any, methodName: string, descriptor: PropertyDescriptor){
    console.log('Function Decorator');
    console.log(target);
    console.log(methodName);
    console.log(descriptor);
}

class Car {
    @FunctionDecorator
    printName(){
        console.log(this.name);
    }
}

Dekorator z argumentami

Możliwe jest stworzenie dekoratora, który może przyjmować argumenty. Aczkolwiek wymaga to troszeczkę innej konstrukcji – fabryki dekoratorów (ang. decorator factory). Brzmi skomplikowanie, ale w rzeczy samej jest to po prostu przyjęcie argumentu oraz zwrócenie funkcji przez dekorator:

function ArgumentsInDecorator(times: number){
    return function(constructorFunction: Function){
        for (let i = 0; i < times; i++) {
            console.log(`I'm a decorator`);
        }
    }
};

@ArgumentsInDecorator(3)
class Car {
    name: string;
    constructor(name: string){
        this.name = name;
    }
}

Podobnie wygląda tworzenie dekoratora z argumentami dla metod i pól klasy.

Warto zajrzeć

1. https://www.typescriptlang.org/docs/handbook/decorators.html
2. Moment i kolejność uruchamiania dekoratorów
3. Nadpisywanie za pomocą dekoratorów

Pozostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *