Dekoratory
Funkcje przypinane znakiem @ do klas i ich elementów, które rozszerzają albo zmieniają ich działanie.
- Zwraca
- Dla klasy, metody i akcesora: element, który zastąpi oryginał, albo nic (bez zmian). Dekorator pola może zwrócić funkcję przekształcającą wartość początkową.
Przykład
#function logged<This, Args extends unknown[], Result>(
method: (this: This, ...args: Args) => Result,
context: ClassMethodDecoratorContext<This>
) {
const name = String(context.name)
return function (this: This, ...args: Args): Result {
console.log(`LOG: ${name}(${args.join(', ')})`)
return method.apply(this, args)
}
}
class ScoreBoard {
private total = 0
@logged
add(points: number) {
this.total += points
return this.total
}
}
const board = new ScoreBoard()
board.add(20)
console.log(board.add(15))LOG: add(20) LOG: add(15) 35
Definicja i zastosowanie
#Dekorator to zwykła funkcja, którą przypinasz znakiem @ do klasy albo jej elementu, np. @logged nad metodą. Uruchamia się raz, w chwili definiowania klasy, i dostaje dekorowany element oraz obiekt context z jego opisem. Jeśli zwróci nową funkcję, zastąpi ona oryginalną metodę. Raz napisaną logikę, np. logowanie, pomiar czasu czy sprawdzanie uprawnień, dołączysz wtedy do dowolnej metody jedną linijką.
TypeScript od wersji 5.0 obsługuje standardowe dekoratory z propozycji TC39, bez żadnej dodatkowej opcji. Można nimi oznaczać klasy, metody, gettery i settery, pola oraz pola z modyfikatorem accessor. Nie działają przy zwykłych funkcjach ani przy parametrach, gdzie kompilator zgłasza „Decorators are not valid here”. Przy target niższym niż ESNext kompilator zamienia dekoratory na zwykły JavaScript z funkcjami pomocniczymi.
Wiele popularnych bibliotek, np. NestJS i TypeORM, korzysta ze starszej, eksperymentalnej wersji dekoratorów, włączanej opcją experimentalDecorators. Takie dekoratory dostają inne argumenty (obiekt docelowy, nazwę i deskryptor właściwości), mogą oznaczać parametry i współpracują z opcją emitDecoratorMetadata. Obie wersje są niezgodne: dekorator napisany dla jednej nie zadziała w drugiej.
Składnia
#function dekorator(value, context) {
return zamiennik // opcjonalnie
}
@dekorator
class Klasa {
@dekorator metoda() { … }
@dekorator pole = 1
}Argumenty dekoratora
#value
zależy od elementu
Dekorowany element: klasa, metoda, getter albo setter, a przy poluaccessorobiekt zgetiset. Przy zwykłych polach toundefined, bo w chwili definiowania klasy pole nie ma jeszcze wartości.context
ClassMethodDecoratorContext i pokrewne
Opis elementu:kind(rodzaj, np."method"albo"field"),name(nazwa),static,privateoraz metodaaddInitializer(), która rejestruje kod uruchamiany przy inicjalizacji.
Więcej przykładów
#function clamp(min: number, max: number) {
return function (_value: undefined, _context: ClassFieldDecoratorContext<unknown, number>) {
return (initial: number) => Math.min(max, Math.max(min, initial))
}
}
class ReaderSettings {
@clamp(12, 24) fontSize = 40
@clamp(1, 3) columns = 0
}
console.log(new ReaderSettings())ReaderSettings { fontSize: 24, columns: 1 }const registry: string[] = []
function register(_value: new (...args: any[]) => object, context: ClassDecoratorContext) {
registry.push(String(context.name))
}
@register
class QuizTask {}
@register
class CodeTask {}
console.log(registry)['QuizTask', 'CodeTask']
Dobre praktyki
#- Dekorator z argumentami, np.
@clamp(12, 24), to funkcja, która zwraca właściwy dekorator. Tak zbudowana jest większość dekoratorów z bibliotek. - Dekorator pola zmienia tylko wartość początkową. Żeby kontrolować każdy późniejszy zapis, oznacz pole słowem
accessori użyj dekoratora, który podmieni jegogetiset. - Sprawdź, czy
tsconfig.jsonnie maexperimentalDecorators: true. Wtedy obowiązują stare sygnatury i przykłady z tej strony nie przejdą kompilacji.
Powiązane hasła
#- public, private, protectedModyfikatory dostępu określające, skąd można używać pól i metod klasy.
- abstract classKlasa bazowa, której nie można utworzyć bezpośrednio, z metodami do uzupełnienia przez potomków.
- implementsDeklaracja, że klasa spełnia kontrakt opisany interfejsem.
- Typy funkcjiTypowanie parametrów, wartości zwracanej i samych funkcji jako wartości.
- Generyki <T>Typy i funkcje z parametrem typu, działające dla wielu typów bez utraty precyzji.
Widzisz błąd albo brakuje przykładu? Napisz do nas.