Sytuacja kobiet w IT w 2024 roku
16.06.20215 min
Martyna Podsiadły
Asseco Poland S.A.

Martyna PodsiadłySoftware DeveloperAsseco Poland S.A.

Zaawansowane typy w TypeScript

Poznaj 5 zaawansowanych typów w TypeScript i dowiedz się, jak je wykorzystywać.

Zaawansowane typy w TypeScript

Pomimo iż wiele osób wciąż kojarzy JavaScript z animacjami na stronach internetowych, język ten wykorzystywany jest również po stronie serwera, w aplikacjach webowych, mobilnych czy desktopowych.

Szybki rozwój oraz coraz szersze spektrum zastosowań spowodowało zapotrzebowanie na dodatkowe możliwości, którymi dysponują inne języki programowania. Z tego powodu Microsoft wprowadził na rynek TypeScript stanowiący pewien nadzbiór JavaScriptu, umożliwiający m.in. statyczne typowanie zmiennych, programowanie obiektowe, czy typy generyczne.

Osoby mające doświadczenie w JavaScript zdają sobie sprawę z tego, jakie konsekwencje może nieść za sobą brak typowania zmiennych. Przykładowo podanie jako argument zmiennej typu string zamiast number w prostym działaniu:

spowoduje konwersję wartości liczbowych do ciągu znaków i ich konkatenację (otrzymamy złączone ze sobą dwa stringi). Metoda addNumbers może przyjąć argumenty jakiegokolwiek typu. JavaScript nie „domyśli się”, że chcemy dodać do siebie dwie liczby.

W JavaScript czeka na nas sporo, często zaskakujących pułapek. Dużym minusem jest brak możliwości wyłapania błędu przy kompilacji. Niestety wszystko poprawnie się skompiluje, ponieważ argumenty a i b w naszej funkcji addNumbers nie mają podanego typu. Właśnie tu z pomocą wkracza TypeScript, w którym jeżeli zatypujemy sobie a i b, a następnie podamy inny typ, otrzymamy błąd już przy kompilacji.

Argument of type 'string' is not assignable to parameter of type 'number'.


Jeżeli cały kod aplikacji jest napisany w TypeScript, to problem z typami teoretycznie nie powinien mieć miejsca (pod warunkiem, że nie używamy namiętnie typu any, co osobiście szczerze odradzam).

W TypeScript mamy oczywiście możliwość tworzenia własnych typów. Nie musimy się ograniczać do tych podstawowych. Często w tym celu tworzymy interfejsy, dzięki którym możemy definiować strukturę obiektu. Aby usprawnić sobie pracę oraz wyeliminować w pewnym stopniu kopiowanie kodu, warto poznać Utility Types. TypeScript zawiera kilkanaście specjalnych typów, dzięki którym możemy tworzyć nowe z już istniejących.

Zdefiniujmy przykładowy interfejs Patient, na podstawie którego utworzymy wiele innych nowych typów.


1. Pick<Type, Keys>
- tworzy nowy typ z wyłącznie wybranymi polami Keys

Wyobraźmy sobie sytuację, w której chcemy zaktualizować dane adresowe pacjenta. Potrzebujemy więc stworzyć obiekt jedynie z polem address oraz id. Pozostałe pola - firstName i lastName pozostają bez zmian. Gdyby nowo stworzony obiekt był typu Patient, w którym wszystkie pola są wymagane musielibyśmy nadmiarowo podać firstName oraz lastName. Szczególnie problematyczne okazałoby się to w przypadku obiektu z wieloma właściwościami – wtedy najprawdopodobniej stworzony by został nowy typ na potrzeby tego przypadku. Nie jest to jednak konieczne. Możemy skorzystać z istniejącego już typu:

Stworzony nowy typ posiada więc jedynie pola id i address. Oczywiście w tym przypadku, jak i w kolejnych poniżej, możemy od razu zatypować sobie obiekt, nie ma potrzeby tworzenia aliasu typu.

W przypadku nie podania wymaganego id lub address od razu otrzymamy błąd.

Property 'address' is missing in type '{ id: number; }' but required in type 'Pick<Patient, "address" | "id">'



2. Partial<Type>
- zwraca typ Type z ustawionymi wszystkimi polami jako opcjonalne

Całkiem często zdarza się, że po prostu nie ma potrzeby podania wszystkich właściwości. Jak wcześniej, potrzebowaliśmy jedynie pola id oraz address. Prostszym sposobem niż Pick jest ustawienie wszystkich pól jako opcjonalne. W przypadku naszego interfejsu Patient utworzony nowy typ PartialPatient nie ma ani jednego wymaganego pola. W takiej sytuacji trzeba jednak pamiętać, aby podać wszystkie pola, których np. oczekuje serwer. Nie zaktualizujemy już danych adresowych pacjenta, jeżeli zapomnimy o podaniu jego id.



3. Omit<Type, Keys> - działa w sposób odwrotny do Pick, czyli usuwa wybrane pola Keys

W takim przypadku nasz nowy typ PatientWithoutAddress posiada id, firstName oraz lastName.



4. Required<Type>
 - odwrotność wspomnianego wcześniej Partial – ustawia wszystkie pola jako wymagane.

Bardzo przydatny typ. W kombinacji z omawianym wcześniej Pick możemy stworzyć typ z jednym wymaganym polem id.



5. Readonly<Type>
- do każdej właściwości dodaje readonly

Próba przypisania wartości do któregoś z pól skutkować będzie wystąpieniem błędu.

Cannot assign to 'id' because it is a read-only property.


Ponadto w TypeScript istnieją również dodatkowe dwa typy - Intersection Typ oraz Union Type, dzięki którym również możemy stworzyć nowy typ. Intersection Type to sposób na łączenie kilku typów w jeden. Używamy w tym celu znaku &, podając, które typy nas interesują. Powstały w ten sposób typ posiada właściwości kilku typów na raz. Union Type z kolei pozwala na opisanie typu, który jest jednym z kilku podanych. Typy oddzielamy za pomocą |. Opisany poniżej nowy typ IntersectionType posiada wszystkie właściwości z Patient i User, natomiast typ Person jest typu Patient lub User

Na koniec Utility Types w wersji generycznej. Dobrym podejściem jest wytwarzanie kodu, który możemy użyć w wielu miejscach w naszej aplikacji. Błędną i niewydajną praktyką w programowaniu jest kopiowanie i wklejanie tych samych fragmentów w wielu miejscach. Spróbujmy połączyć wszystkie sposoby wcześniej opisane, tworząc nowy typ, z którego będziemy mogli skorzystać w wielu różnych przypadkach.

Wykorzystujemy tu Partial, Omit, Required oraz Intersection Type, jednak już nie jedynie dla typu Patient jak wcześniej, ale dla jakiekolwiek zadanego. Co właściwie powstanie z takiej kombinacji?

W pierwszej części Partial<Omit<T, K>> usuwa właściwość K z T i pozostałe pola ustawia jako opcjonalne. W drugiej części Required<Pick<T, K>> sprawia, że zadana właściwość K ustawiana jest jako wymagana. Połączenie obu skutkuje stworzeniem typu z jednym wymaganym K i wszystkimi pozostałymi polami opcjonalnymi. Możemy tu użyć stworzonego wcześniej Patienta (ale może to być tak naprawdę jakikolwiek typ) i uzyskamy poniższy rezultat

Podsumowanie

Oczywiście to nie wszystkie z dostępnych typów w TypeScript. Z ciekawszych wymienić mogę jeszcze Record<K,T> czy NonNullable, lecz do dalszej lektury polecam oficjalną dokumentację. Znajomość możliwości, jakimi dysponujemy w TypeScript, pozwala nam wytwarzać lepszy, czytelniejszy kod, który łatwiej jest też utrzymać.

<p>Loading...</p>