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
