TypeScript, bazujący na JavaScript, jest językiem programowania z silnym systemem typów, który oferuje zaawansowane narzędzia do zastosowań na szeroką skalę. Jego powstanie miało na celu eliminację problemów, które pojawiały się podczas pracy z JavaScript. Kluczowym rozwiązaniem, które wprowadza TypeScript, jest rygorystyczne wykorzystanie typów, co minimalizuje potencjalne błędy.
W TypeScript każda wartość ma przypisany typ. Kompilator weryfikuje zgodność każdej wartości z regułami zdefiniowanymi dla jej typu. Dzięki temu, TypeScript jest w stanie wychwycić błędy w kodzie źródłowym, jeszcze przed uruchomieniem programu.
Proces ten nazywany jest statycznym sprawdzaniem typów. Polega on na wczesnym identyfikowaniu potencjalnych problemów w oparciu o typy danych użytych w programie, co znacząco przyspiesza proces tworzenia oprogramowania.
Oprócz statycznej kontroli typów, która zwiększa przejrzystość i czytelność kodu, TypeScript oferuje dodatkowe mechanizmy, które podnoszą poziom jego użyteczności. Mowa tu między innymi o dekoratorach, które wpływają na możliwość ponownego wykorzystania i łatwość utrzymania kodu.
Dekoratory w TypeScript
Dekoratory w TypeScript to swoiste „ulepszacze” kodu, które pozwalają na modyfikację jego zachowania w trakcie działania programu, a także umożliwiają dodawanie metadanych. To podejście metaprogramowania, gdzie programy traktują inne programy jako dane, by modyfikować swoje zachowanie.
Dekoratory są w zasadzie funkcjami, które są uruchamiane w czasie wykonywania w celu realizacji określonej logiki, gdy tylko nastąpi odczyt lub zmiana dekorowanego elementu.
W ten sposób można rozszerzyć funkcjonalność elementów. Dekoratory w TypeScript mogą być stosowane do klas, metod, właściwości, akcesorów (gettery i settery) oraz parametrów funkcji.
W TypeScript, dekoratory są poprzedzane znakiem „@” i występują w formie „@wyrażenie”, gdzie wyrażenie określa funkcję, która ma zostać wykonana w trakcie działania programu. Ogólna struktura użycia dekoratorów prezentuje się następująco:
@nazwaDekoratora elementDoZdekroowania
Poniżej przedstawiono przykład prostego dekoratora klasy:
function rejestrujKlase(cel: Function) { console.log("Wywołano dekorator rejestrujKlase"); console.log("Klasa:", cel); } @rejestrujKlase // @rejestrujKlase jest dekoratorem class MojaKlasa { constructor() { console.log("Utworzono instancję MojaKlasa"); } } const mojaInstancja = new MojaKlasa();
Efekt wykonania powyższego kodu wygląda następująco:
Wynik:
Wywołano dekorator rejestrujKlase Klasa: [class MojaKlasa] Utworzono instancję MojaKlasa
Funkcja `rejestrujKlase()` przyjmuje jeden argument typu `Function`, nazywany `cel`. Argument `cel` ma typ `Function`, ponieważ otrzymuje konstruktor dekorowanej klasy.
Aby użyć `rejestrujKlase()` jako dekoratora dla klasy `MojaKlasa`, umieszczamy `@rejestrujKlase` bezpośrednio przed deklaracją `MojaKlasa`. Nazwa dekoratora musi być identyczna z nazwą funkcji, której chcemy użyć do dekoracji elementu.
W momencie tworzenia instancji `MojaKlasa`, oprócz konstruktora klasy, wykonywane jest również zachowanie dekoratora, co widać w wynikach.
Aktualnie, dekoratory w TypeScript są funkcją eksperymentalną. Zatem, aby móc z nich korzystać, należy włączyć opcję `experimentalDecorators` w ustawieniach kompilatora w pliku `tsconfig.json`.
Aby to zrobić, wygeneruj plik `tsconfig.json` w folderze projektu TypeScript za pomocą polecenia w terminalu:
tsc --init
Następnie w pliku `tsconfig.json`, usuń komentarz z linii z `experimentalDecorators`, jak pokazano poniżej:
Dodatkowo, ustaw docelową wersję JavaScript na przynajmniej ES2015.
Znaczenie Dekoratorów w TypeScript
O jakości kodu decyduje jego czytelność, możliwość ponownego wykorzystania oraz łatwość utrzymania. Czytelny kod to taki, który jest zrozumiały i jasno komunikuje intencje programisty innym osobom pracującym z kodem.
Kod wielokrotnego użytku pozwala na adaptację i ponowne użycie komponentów, takich jak funkcje czy klasy, w różnych częściach aplikacji bez konieczności wprowadzania znaczących modyfikacji.
Z kolei kod łatwy w utrzymaniu to taki, który można łatwo modyfikować, aktualizować i naprawiać przez cały cykl jego życia.
Dekoratory w TypeScript wspomagają osiągnięcie wszystkich tych celów. Pozwalają na rozszerzenie zachowania kodu z wykorzystaniem deklaratywnej składni. Logikę można zamknąć w dekoratorach i wywoływać je tam, gdzie jest to konieczne, co sprawia, że kod jest bardziej zrozumiały i czytelny.
Dekoratory nie są elementami jednorazowego użytku. Ich naturą jest możliwość ponownego wykorzystania. Zdefiniowany dekorator można zaimportować i użyć w dowolnym miejscu kodu, gdzie modyfikacja zachowania jest potrzebna. Pozwala to uniknąć powielania kodu.
Dodatkowo, dekoratory zapewniają dużą elastyczność i modułowość, umożliwiając rozdzielenie funkcjonalności na niezależne komponenty. Dzięki temu, w połączeniu z czytelnym kodem, dekoratory TypeScript znacznie ułatwiają utrzymanie kodu w przyszłości.
Rodzaje Dekoratorów w TypeScript
Dekoratory w TypeScript można stosować do klas, właściwości klas, metod klas, akcesorów i parametrów metod. W zależności od elementu, który dekorujemy, wyróżniamy różne typy dekoratorów. Są to:
#1. Dekorator Klasy
Dekorator klasy służy do obserwowania, modyfikowania lub zastępowania definicji klasy. Deklaruje się go bezpośrednio przed klasą, którą dekoruje. Dekorator klasy jest wywoływany z konstruktorem dekorowanej klasy jako jedynym argumentem.
Poniżej przedstawiono dekorator klasy, który uniemożliwia rozszerzanie klasy:
function zamroz(cel: Function) { Object.freeze(cel); Object.freeze(cel.prototype) } @zamroz class Pojazd { kola: number = 4; constructor() { console.log("Utworzono pojazd"); } } class Auto extends Pojazd { constructor() { super(); console.log("Utworzono auto"); } } console.log(Object.isFrozen(Pojazd));
Aby uniemożliwić rozszerzanie klasy, wykorzystujemy funkcję `Object.freeze()` i przekazujemy klasę do zamrożenia. Dekorator służy do dodania tej funkcjonalności do klasy. Możemy sprawdzić czy klasa `Pojazd` jest zamrożona za pomocą `isFrozen()`. Efekt działania powyższego kodu to:
true
#2. Dekorator Właściwości
Dekorator właściwości służy do dekorowania właściwości klasy i deklaruje się go bezpośrednio przed dekorowaną właściwością. Za pomocą dekoratorów właściwości możemy obserwować lub zmieniać definicję właściwości.
W czasie działania programu, dekorator ten przyjmuje dwa argumenty. Pierwszym jest funkcja konstruktora, gdy dekorowany element jest statyczny, albo prototyp klasy, jeśli jest to właściwość instancji. Drugim argumentem jest nazwa dekorowanej właściwości.
W TypeScript, elementy statyczne są oznaczane słowem kluczowym `static`. Dostęp do tych elementów można uzyskać bez tworzenia instancji klasy. Elementy instancji nie posiadają tego słowa kluczowego i dostęp do nich wymaga utworzenia instancji klasy.
Przykład dekoratora właściwości:
function dekoratorKola(cel: any, nazwaWlasciwosci: string) { console.log(nazwaWlasciwosci.toUpperCase()) } class Pojazd { @dekoratorKola kola: number = 4; constructor() { console.log("Utworzono pojazd"); } }
Wynik działania kodu:
KOLA
#3. Dekorator Metody
Dekorator metody jest deklarowany przed deklaracją metody i służy do obserwowania, modyfikowania lub zastępowania definicji metody. Przyjmuje trzy argumenty: funkcję konstruktora klasy (dla elementu statycznego) lub prototyp klasy (dla elementu instancji), nazwę elementu i deskryptor właściwości elementu. Deskryptor właściwości to obiekt dostarczający informacji o atrybutach i zachowaniu właściwości.
Dekoratory metod przydają się, gdy chcemy wykonać jakąś akcję przed lub po wywołaniu metody, lub np. rejestrować informację o wywołaniu. Możemy z ich pomocą sygnalizować, że metoda jest przestarzała (dostępna, ale niezalecana do użycia).
Przykład dekoratora metody:
const rejestrujPrzestarzalaMetode =(cel: any, nazwaMetody: string, deskryptor: PropertyDescriptor) => { console.log(`${nazwaMetody} jest przestarzała`); console.log(deskryptor); } class Pojazd { kola: number = 4; constructor() { console.log("Utworzono pojazd"); } @rejestrujPrzestarzalaMetode zatankuj(): void { console.log("Trwa tankowanie pojazdu"); } }
Wynik:
zatankuj jest przestarzała { value: [Function: zatankuj], writable: true, enumerable: false, configurable: true }
#4. Dekoratory Akcesorów
W TypeScript wyróżniamy dwa typy metod dostępu: `get` i `set`. Metody akcesorów służą do kontrolowania dostępu do właściwości klasy. Dekoratory akcesorów służą do dekorowania tych metod i są deklarowane przed definicją akcesora. Dekoratory akcesorów działają w sposób analogiczny do dekoratorów metod.
Przykład dekoratorów akcesorów:
const rejestrujKola =(cel: any, nazwaAkcesora: string, deskryptor: PropertyDescriptor) => { console.log(`${nazwaAkcesora} służy do pobierania liczby kół`); console.log(deskryptor); } class Pojazd { private kola: number = 4; constructor() { console.log("Utworzono pojazd"); } @rejestrujKola get liczbaKol(): number { return this.kola; } }
Wynik:
liczbaKol służy do pobierania liczby kół { get: [Function: get liczbaKol], set: undefined, enumerable: false, configurable: true }
W przypadku dekoratorów akcesorów, nie można stosować dekoratorów do par get/set o tej samej nazwie. Na przykład, w powyższym kodzie, jeśli utworzymy setter `set liczbaKol`, nie możemy użyć dekoratora `rejestrujKola` na nim.
#5. Dekoratory Parametrów
Dekorator parametrów służy do obserwowania parametru zadeklarowanego w metodzie i jest deklarowany przed deklaracją parametru. Dekoratory parametrów przyjmują trzy argumenty: konstruktor klasy (dla elementu statycznego) lub prototyp klasy (dla elementu instancji), nazwę elementu i indeks parametru na liście parametrów funkcji (liczony od 0).
Przykład dekoratora parametrów:
const rejestrujPasazera = (cel: Object, nazwaWlasciwosci: string, indeksParametru: number) => { console.log(`Dekorator parametru ${nazwaWlasciwosci} o indeksie ${indeksParametru}`); } class Pojazd { private kola: number = 4; constructor() { console.log("Utworzono pojazd"); } odbierzPasazera(miejsce: string, liczbaPasazerow: string, @rejestrujPasazera kierowca: string) { console.log(`${liczbaPasazerow} odebrano w ${miejsce} przez ${kierowca}`); } wysadzPasazera(kierowca: string, @rejestrujPasazera miejsce: string, liczbaPasazerow: string) { console.log(`${liczbaPasazerow} wysadzono w ${miejsce} przez ${kierowca}`); } }
Wynik:
Dekorator parametru odbierzPasazera o indeksie 2 Dekorator parametru wysadzPasazera o indeksie 1
Podsumowanie
Dekoratory w TypeScript znacznie poprawiają czytelność kodu i ułatwiają tworzenie kodu wielokrotnego użytku. Możliwość zdefiniowania dekoratora raz i wielokrotnego jego wykorzystania, wpływa pozytywnie na łatwość utrzymania kodu.
Mimo że dekoratory są nadal funkcją eksperymentalną, są bardzo przydatne i warto się z nimi zapoznać.
Możesz również przeczytać, jak przekonwertować ciąg znaków na liczbę w TypeScript.