Opinie o książce TypeScript. Skuteczne programowanie

Książka eksploruje najbardziej powszechne pytania, na które napotykamy się podczas pracy w TypeScript i dostarcza praktycznych porad zorientowanych na wyniki. Bez względu na poziom doświadczenia, każdy może się czegoś nauczyć z tej książki.

– Ryan Cavanaugh, szef inżynierii TypeScript w firmie Microsoft

Drugie wydanie TypeScript. Skuteczne programowanie to wspaniałe uzupełnienie już doskonałej książki. Łączy świetne wskazówki i sztuczki z pierwszego wydania z pogłębionymi sugestiami dotyczącymi najlepszych praktyk, które jedynie utrwaliły się w ostatnich latach. Zawarta w książce wiedza jest niezbędna dla skutecznej pracy nad projektem TypeScript dowolnych rozmiarów. Szczególnie doceniam to, że większości zaleceń towarzyszą uwagi na temat niuansów – tego, kiedy i dlaczego należy je stosować. Każdego dewelopera, który chce pracować efektywnie i pisać skuteczny kod TypeScript, zachęcam do lektury tej książki – i to co najmniej dwa razy, ze szczegółowymi notatkami.

– Josh Goldberg, deweloper open source i autor książki Poznaj TypeScript¹

TypeScript. Skuteczne programowanie nie jest po prostu przewodnikiem po jednym z najbardziej popularnych języków programowania. W istocie uczy nas, jak myśleć typami poprzez praktyczne porady z rzeczywistego świata. Z każdej strony wyziera przebogate doświadczenie Autora. Ta odwaga! A potencjalnie pozwoli zmienić nasze spojrzenie na programowanie, także poza TypeScriptem.

– Stefan Baumgartner, starszy architekt produktu w Dynatrace oraz autor książki TypeScript Cookbook

TypeScript. Skuteczne programowanie, wyd. 2, to doskonała uzupełniająca książka dla inżynierów, którzy już znają podstawy TypeScriptu i albo na co dzień piszą kod w tym języku, albo rozważają włączenie TypeScriptu do swojej bazy kodu.

– Boris Cherny, autor książki Programowanie w TypeScript²

Posługuję się językiem TypeScript od ponad 10 lat, zarówno w uczeniu go, jak i profesjonalnym stosowaniu. TypeScript. Skuteczne programowanie oferuje solidne porady doskonale pasujące do moich własnych doświadczeń. Książka skutecznie podkreśla to, że znajomość składni TypeScriptu to jedna rzecz, ale umiejętność używania jego funkcjonalność to zupełnie co innego. Jest to pomocny przewodnik dla tych, którzy już znają ten język, ale pragną wyostrzyć swoje umiejętności posługiwania się nim.

– Titian-Cornel Cernicova-Dragomir; inżynier oprogramowania, Bloomberg LP

Książka wypchana praktycznymi poradami, która musi znaleźć miejsce na biurku każdego profesjonalnego programisty TypeScript. Nawet jeśli ktoś sądzi, że już zna TypeScript, nie będzie żałować czasu poświęconego na lekturę.

– Yakov Fain, mistrz języka Java

To nie jest po prostu książka o tym, co TypeScript może zrobić – pokazuje, dlaczego poszczególne funkcjonalności języka są użyteczne i gdzie stosować pokazywane wzorce, aby uzyskać najlepsze wyniki. Szczególnie podoba mi się skupienie się na praktycznych poradach, które są przydatne w codziennej pracy, dopełnione dokładnie taką dawką teorii, jaka jest potrzebna dla zrozumienia działania. Sam siebie uważam za zaawansowanego użytkownika TypeScriptu, ale z tej książki nauczyłem się mnóstwa nowych rzeczy.

—Jesse Hallett, starszy inżynier oprogramowania, Hasura

1 Wydanie polskie APN Promise 2022, ISBN 978-83-7541-498-1

2 Wydanie polskie Helion 2020, ISBN 978-83-012-1122-6

TypeScript Skuteczne programowanie, wyd. 2

83 sposoby ulepszania kodu TypeScript

Dan Vanderkam

przekład: Joanna Zatorska Marek Włodarz

APN Promise

Warszawa 2024

TypeScript. Skuteczne programowanie. Wydanie 2

© 2024 APN PROMISE SA

Authorized translation of English edition of Effective TypeScript, 2nd Edition

ISBN 978-1-492-15500-3

Copyright © 2024 Dan Vanderkam. All rights reserved.

This translation is published and sold by permission of O’Reilly Media, Inc., which owns or controls of all rights to publish and sell the same.

APN PROMISE SA, ul. Domaniewska 44a, 02-672 Warszawa; tel. +48 22 35 51 600; e-mail: wydawnictwo@promise.pl

Wszystkie prawa zastrzeżone. Żadna część niniejszej książki nie może być powielana ani rozpowszechniana w jakiejkolwiek formie i w jakikolwiek sposób (elektroniczny, mechaniczny), włącznie z fotokopiowaniem, nagrywaniem na taśmy lub przy użyciu innych systemów bez pisemnej zgody wydawcy.

Logo O’Reilly jest zarejestrowanym znakiem towarowym O’Reilly Media, Inc. Ilustracja z okładki i powiązane elementy są znakami towarowymi O’Reilly Media, Inc.

Wszystkie inne nazwy handlowe i towarowe występujące w niniejszej publikacji mogą być znakami towarowymi zastrzeżonymi lub nazwami zastrzeżonymi odpowiednich firm odnośnych właścicieli.

Przykłady firm, produktów, osób i wydarzeń opisane w niniejszej książce są fikcyjne i nie odnoszą się do żadnych konkretnych firm, produktów, osób i wydarzeń. Ewentualne podobieństwo do jakiejkolwiek rzeczywistej firmy, organizacji, produktu, nazwy domeny, adresu poczty elektronicznej, logo, osoby, miejsca lub zdarzenia jest przypadkowe i niezamierzone.

APN PROMISE SA dołożyła wszelkich starań, aby zapewnić najwyższą jakość tej publikacji. Jednakże nikomu nie udziela się rękojmi ani gwarancji. APN PROMISE SA nie jest w żadnym wypadku odpowiedzialna za jakiekolwiek szkody będące następstwem korzystania z informacji zawartych w niniejszej publikacji, nawet jeśli APN PROMISE została powiadomiona o możliwości wystąpienia szkód.

ISBN: 978-83-7541-560-5 (druk), 978-83-7541-561-2 (ebook)

Projekt okładki: Karen Montgomery; Ilustracje: Rebecca Demarest; Ilustracja na okładce: Jose Marzan

Przekład: Joanna Zatorska, Marek Włodarz; Korekta: Ewa Swędrowska; Skład i łamanie: MAWart Marek Włodarz

Dla Alex.Jesteś w moim typie.

Spis treści

Przedmowa do drugiego wydania

Przedmowa do pierwszego wydania

Poznajemy TypeScript

Element 1: Relacja między TypeScript a JavaScript

Element 2: Które opcje TypeScript wykorzystujemy

Element 3: Generowanie kodu jest niezależne od typów

Element 4: Przyzwyczaj się do strukturalnego typowania

Element 5: Ograniczanie użycia typu any

System typowania TypeScript

Element 6: Używanie edytora do sprawdzania i eksploracji systemu typowania

Element 7: Typy jako zbiory wartości

Element 8: Ustalanie, czy symbol należy do przestrzeni typu czy do przestrzeni wartości

Element 9: Deklarujmy typy zamiast stosować asercje typów

Element 10: Unikajmy typów opakowujących obiekty (String, Number, Boolean, Symbol, BigInt)

Element 11: Limity testowania dodatkowych właściwości

Element 12: Stosujmy typy w całych wyrażeniach funkcyjnych, gdy jest to możliwe

Element 13: Odróżniajmy typ od interfejsu

Element 14: Używajmy readonly, aby uniknąć błędów związanych z mutowaniem

Element 15: Używajmy operacji typów i typów generycznych, aby uniknąć powtórzeń

Element 16: Korzystajmy z sygnatur indeksów dla danych dynamicznych

Element 17: Unikanie liczbowych sygnatur indeksów

Wnioskowanie typów i analiza przepływu sterowania

Element 18: Unikajmy zaśmiecania kodu typami, które można wywnioskować

Element 19: Używajmy różnych zmiennych dla różnych typów

Element 20: Rozszerzanie typów

Element 21: Tworzenie całych obiektów od razu

Element 22: Zawężanie typów

Element 23: Zachowajmy spójność w używaniu aliasów

Element 24: Jak wykorzystuje się kontekst podczas wnioskowania typów

Element 25: Ewolucja typu any

Element 26: Korzystajmy z konstrukcji funkcyjnych i bibliotek, aby ułatwić przepływ typów

Element 27: Używajmy funkcji asynchronicznych zamiast wywołań zwrotnych

Element 28: Używanie klas i rozwijania funkcji do tworzenia nowych punktów wnioskowania

Projektowanie typów

Element 29: Preferujmy typy, które zawsze reprezentują poprawne stany

Element 30: Liberalne podejście do akceptowanych wartości i surowe do zwracanych danych

Element 31: Nie powtarzajmy informacji o typie w dokumentacji

Element 32: Unikajmy dołączania null lub undefined w aliasach typu

Element 33: Przenośmy wartości null poza obręb swojego typu

Element 34: Używajmy unii interfejsów zamiast interfejsów z uniami

Element 35: Preferujmy bardziej precyzyjne alternatywy typów łańcuchowych

Element 36: Używanie oddzielnego typu dla wartości specjalnych

Element 37: Ograniczanie użycia właściwości opcjonalnych

Element 38: Unikajmy wielokrotnych parametrów o tym samym typie

Element 39: Preferujmy typy ujednolicone do modelowania różnic

Element 40: Używajmy typów niekompletnych zamiast nieprecyzyjnych

Element 41: Nazywajmy typy zgodnie z językiem dziedziny swojego projektu

Element 42: Unikajmy typów opartych na danych anegdotycznych

Niesolidność i typ any

Element 43: Używanie możliwie najwęższego zakresu dla typów any

Element 44: Preferujmy warianty bardziej precyzyjne od zwykłego typu any

Element 45: Ukrywajmy nie gwarantujące bezpieczeństwa asercje typów w dobrze typowanych funkcjach

Element 46: Korzystajmy z typu unknown zamiast any dla wartości nieznanego typu

Element 47: Stosujmy bezpieczne pod kątem typów podejście do małpiego łatania

Element 48: Unikanie pułapek (nie)solidności

Element 49: Śledźmy pokrycie typami, aby zapobiec regresji bezpieczeństwa typów

Generyki i programowanie na poziomie typów

Element 50: Traktujmy typy generyczne jak funkcje pomiędzy typami

Element 51: Unikajmy niepotrzebnych parametrów typów

Element 52: Używajmy typów warunkowych zamiast przeciążonych deklaracji

Element 53: Jak kontrolować rozkład unii przez typy warunkowe

Element 54: Używanie szablonów typów literałów do modelowania DSL i zależności pomiędzy ciągami

Element 55: Piszmy testy swoich typów

Element 56: Zwróćmy uwagę na to, jak typy są pokazywane

Element 57: Preferujmy typy generyczne z rekurencją ogonową

Element 58: Rozważmy generowanie kodu jako alternatywę dla złożonych typów

Przepisy dla TypeScript

Element 59: Używanie typów never do sprawdzania kompletności

Element 60: Iterowanie po obiektach

Element 62: Korzystajmy z typów mapowanych, aby zapewnić synchronizację wartości

Element 62: Używajmy parametrów resztowych i typów krotek do modelowania funkcji o zmiennej liczbie argumentów

Element 63: Używajmy opcjonalnych właściwości never do modelowania alternatywy wykluczającej

Element 64: Rozważmy stosowanie „etykiet” dla typowania nominalnego

Deklaracje typów i składnia @types

Element 65: Umieszczanie kodu TypeScript i @types w deklaracjach devDependencies

Element 66: Trzy numery wersji w deklaracjach typów

Element 67: Eksportujmy wszystkie typy obecne w publicznych API

Element 68: Używajmy TSDoc do tworzenia komentarzy API

Element 69: Zdefiniujmy typ dla this w wywołaniach zwrotnych

Element 70: Używajmy typów lustrzanych dla oddzielania zależności

Element 71: Używajmy rozszerzania modułu do ulepszania typów

Pisanie i uruchamianie kodu

Element 72: Korzystajmy z funkcjonalności standardu ECMAScript zamiast TypeScript

Element 73: Korzystajmy z map kodu źródłowego do debugowania kodu TypeScript

Element 74: Rekonstruowanie typów w czasie działania

Element 75: Hierarchia DOM

Element 76: Tworzenie dokładnego modelu środowiska

Element 77: Zależności pomiędzy sprawdzaniem typów a testami jednostkowymi

Element 78: Zwracajmy uwagę na wydajność kompilatora

Modernizacja i migracja

Element 79: Pisanie nowoczesnego kodu JavaScript

Element 80: Używajmy @ts-check oraz JSDoc do eksperymentowania z TypeScript

Element 81: Używajmy allowJs, aby połączyć kod TypeScript z JavaScript

Element 82: Przekształcajmy moduły po kolei, wędrując w górę grafu zależności

Element 83: Nie uważajmy migracji za zakończoną przed włączeniem opcji noImplicitAny

Odwzorowanie elementów pomiędzy pierwszym i drugim wydaniem

Indeks

O autorze

Polecamy także:

Przedmowa do drugiego wydania

Aż trudno uwierzyć, że od publikacji pierwszego wydania tej książki minęło prawie pięć lat. Książka i towarzysząca jej witryna (https://effectivetypescript.com) zostały przyjęte bardzo dobrze i niezliczonym deweloperom pomogły rozwinąć i podnieść swoją znajomość i posługiwanie się tym językiem.

Byłem zaskoczony, jak szybko zacząłem otrzymywać pytania, czy książka nie straciła aktualności. Wystarczyło zaledwie sześć miesięcy! Biorąc pod uwagę tempo zmian, jakie następowały w języku TypeScript w latach poprzedzających pierwsze wydanie, było to dla mnie rzeczywistym zmartwieniem. Starałem się uniknąć sytuacji drukowania książki, która szybko się zestarzeje. Oznaczało to koncentrację raczej na tematach ponadczasowych, takich jak podstawy języka i projektowanie programów, niż na bibliotekach i frameworkach. I mówiąc ogólnie, materiał zawarty w książce spisał się dobrze.

Rozwój TypeScript i uzyskiwanie przez niego nowych funkcjonalności nie tyle unieważniło treść pierwszego wydania, co sprawiło pojawienie się luk. Pisanie „skutecznego” elementu wymaga nieco więcej, niż sama wiedza o tym, jak działa jakaś funkcjonalność. Konieczne jest doświadczenie w posługiwaniu się tą funkcjonalnością: czas spędzony na nauce, jakie wzorce działają dobrze, a które się nie sprawdzają. Typy warunkowe zostały dodane do języka dopiero w roku 2019, zatem przy pisaniu pierwszego wydania miałem niewielkie doświadczenia na ich temat. Zostały bardziej wyczerpująco omówione w tym wydaniu. Szablonowe typy literałów są największym rozszerzeniem TypeScript w ostatnich pięciu latach. Otworzyły całe nowe światy możliwości i przedstawiłem je w Elemencie 54.

Co więcej, dzięki takim projektom, jak Type Challenges (https://tsch.js.org), twórcy TypeScript stali się znacznie bardziej ambitni w tym, co robią w systemie typów. W pierwszym wydaniu jedynie pobieżnie omówiłem generyki i programowanie na poziomie typów. Teraz zagadnienia te otrzymały cały nowy rozdział 6.

Po przeszło ośmiu latach od moich pierwszych prób używania TypeScript nadal sprawia mi to przyjemność i ekscytuję się za każdym razem, gdy czytam informacje o najnowszym wydaniu lub widzę nowy ambitny tekst Andersa Hejlsberga krążący po sieci. Nadal lubię pomagać innym programistom w nauce TypeScript i poprawiać jego wykorzystanie. Mam nadzieję, że widać to na tych stronach i że lektura tej książki sprawi, że praca w TypeScript będzie ci sprawiać równie dużo przyjemności, jak mnie!

Wallkill, NY; marzec 2024

Dla kogo jest ta książka

Książki z serii Skuteczne programowanie zostały opracowane jako „standardowe drugie podręczniki” do przedmiotu, który omawiają. Największe korzyści z książki TypeScript. Skuteczne programowanie odniosą osoby, które mają już praktyczne doświadczenie w pisaniu kodu JavaScript i TypeScript. Pisząc tę książkę nie miałem zamiaru uczyć nikogo programowania w TypeScript lub JavaScript, ale ułatwić przejście z poziomu początkującego lub średnio zaawansowanego programisty do poziomu eksperta. Możemy to osiągnąć dzięki elementom, na które podzielona jest ta książka. Ułatwią nam one tworzenie modeli myślowych działania języka i ekosystemu TypeScript, uczulą na pułapki, których należy unikać i zaprezentują możliwości TypeScript, oraz pokażą, jak z nich korzystać w najbardziej skuteczny sposób. Podczas gdy typowe podręczniki przedstawiają kilka sposobów, jakimi język umożliwia wykonanie jakiegoś zadania, ta książka wyjaśnia, który z tych kilku sposobów należy wybrać i dlaczego.

TypeScript ewoluował szybko przez ostatnie lata, lecz mam nadzieję, że jest już na tyle ustabilizowany, że zawartość tej książki będzie aktualna przez kilka następnych lat. Ta książka koncentruje się przede wszystkim na samym języku, a nie na platformach czy narzędziach do budowania. Nie znajdziemy tu przykładów korzystania z platform React lub Vue z TypeScript, ani konfigurowania TypeScript w celu współpracy z webpack lub Vite. Zalecenia zawarte w tej książce przydadzą się wszystkim użytkownikom TypeScript.

Dlaczego napisałem tę książkę

Gdy zacząłem pracować w Google, dostałem egzemplarz trzeciego wydania książki Skuteczny nowoczesny C++ autorstwa Scotta Meyersa¹. Okazała się zupełnie inna niż wszystkie pozostałe książki programistyczne, które dotychczas czytałem. Nie starała się być zrozumiała dla początkujących programistów. Nie obejmowała też wszystkich funkcji języka. Zamiast przedstawiać działanie różnorodnych cech C++, informowała, których z nich używać, a których nie. Wszystkie informacje były podzielone na kilkadziesiąt krótkich specyficznych punktów poświęconych konkretnym przykładom.

Czytanie tych wszystkich przykładów podczas codziennego korzystania z języka było świetnym doświadczeniem. Pisałem już wcześniej programy w C++, ale po raz pierwszy poczułem swobodę tworzenia w tym języku. Dowiedziałem się też, jak postrzegać poszczególne opcje, które zostały przedstawione w książce. W kolejnych latach podobne wrażenie towarzyszyło mi podczas czytania książek Java. Efektywne programowanie Joshuy Blocha² i Efektywny JavaScript Davida Hermana³.

Jeśli ktoś swobodnie posługuje się kilkoma różnymi językami programistycznymi, wówczas nauka tajników nowego języka stanowi wyzwanie dla modeli myślowych i poznawania różnic między tymi językami. Pisząc tę książkę poznałem mnóstwo nowych aspektów TypeScript. Mam nadzieję, że czytelnicy odniosą takie same korzyści!

Organizacja książki

Ta książka zawiera kolekcję „Elementów”, z których każdy jest swego rodzaju technicznym esejem, oferującym konkretne porady dotyczące określonego aspektu TypeScript. Elementy są pogrupowane tematycznie w rozdziały, lecz zachęcam do swobodnego przeglądania i czytania najbardziej interesujących nas Elementów.

Tytuł każdego elementu podkreśla kluczowe zalecenie. Są to rzeczy, o których trzeba pamiętać korzystając z TypeScript, a zatem warto przejrzeć spis treści, aby zachować je w pamięci. Jeśli na przykład piszesz dokumentację i wydaje ci się, że nie należy uwzględniać informacji o typach, powinieneś przeczytać Element 31: Nie powtarzajmy informacji o typie w dokumentacji.

Treść elementu omawia zalecenie podane w tytule, przedstawia odpowiednie przykłady i prezentuje argumenty techniczne. Niemal każdy punkt przedstawiony w tej książce jest poparty przykładowym kodem. Zwykle podczas czytania książek technicznych analizuję przykłady i przeglądam tekst. Podejrzewam, że tak samo postępują czytelnicy tej książki. Mam nadzieję, że przeczytają tekst i wyjaśnienia! Jednak nawet jeśli będziemy jedynie analizować przykłady, powinniśmy zrozumieć najważniejsze kwestie.

Po przeczytaniu każdego elementu czytelnicy powinni zrozumieć, dlaczego zawarte w nim zalecenie ułatwi im skuteczne korzystanie z TypeScript. Będą także wiedzieć, czy dane zalecenia należy stosować w określonych sytuacjach. Scott Meyers, autor książki Skuteczny nowoczesny C++, prezentuje zapadający w pamięć przykład. Kiedyś spotkał się z zespołem programistów, którzy napisali oprogramowanie sterujące pociskami. Wiedzieli, że mogą zignorować jego porady o zapobieganiu wyciekom zasobów, ponieważ ich oprogramowanie zakończy działanie, gdy pocisk trafi w cel, a sprzęt ulegnie zniszczeniu. Nie znam żadnych pocisków sterowanych kodem JavaScript, jednak kod w tym języku obsługuje teleskop Kosmiczny Teleskop Jamesa Webba, a zatem wszystko jest możliwe!

Na końcu każdego elementu znajduje się sekcja „Do zapamiętania”, która zawiera kilka punktów z podsumowaniem tego Elementu. Jeśli ktoś tylko przegląda treść książki, może przeczytać te punkty, aby uzyskać ogólny pogląd na zagadnienia poruszane w tym Elemencie i dowiedzieć się, czy chciałby przeczytać więcej. Warto jednak przeczytać treść Elementu! W ostateczności wystarczy samo podsumowanie.

Konwencje wykorzystywane w przykładowym kodzie TypeScript

Wszystkie przykłady kodu są napisane w TypeScript z wyjątkiem sytuacji, gdy z kontekstu wynika, że są w formacie JSON, GraphQL lub w jakimś innym języku. Duża część posługiwania się TypeScript wymaga iterakcji z edytorem, co nie zawsze można dobrze zaprezentować w druku. W tym celu korzystam z kilku konwencji.

Większość edytorów wyświetla błędy za pomocą pofalowanego podkreślenia. Aby sprawdzić cały komunikat błędu, należy umieścić kursor myszy nad podkreślonym tekstem. Aby przedstawić błąd w przykładowym kodzie, umieściłem znaki tyldy w miejscu wystąpienia błędu:

let str = 'not a number';let num: number = str; // ~~~ Typu 'string' nie można przypisać do typu 'number'4

Czasem edytuję komunikaty błędów, aby zwiększyć czytelność, lecz nigdy nie usuwam błędu. Jeśli ktoś skopiuje przykład i wklei go do edytora, powinien uzyskać dokładnie te same błędy, ani więcej, ani mniej.

Aby podkreślić brak błędów, korzystam z zapisu // OK:

let str = 'not a number';let num: number = str as any; // OK

Każdy powinien móc przesunąć kursor nad symbol w edytorze, aby sprawdzić typ ustalony przez TypeScript. Aby oznaczyć to w tekście, stosuję komentarze używające składni Twoslash (^?):

let v = {str: 'hello', num: 42};// ^? let v: { str: string; num: number; }

Rysunek W-1 Składnia Twoslash w środowisku Playground

Dokładnie taki sam typ ujrzymy w swoim edytorze, gdy umieścimy kursor nad symbolem powyżej znaku daszka ^. Jest to zgodne z konwencją używaną w świecie TypeScript. Jeśli ktoś skopiuje przykład kodu i wklei go za sekwencją ^?, TypeScript wypełni za nas resztę. To, co zobaczymy w środowisku Playground (patrz rysunek W-1), powinno dokładnie pasować do tego, co widać w książce.

Czasem będę korzystał z instrukcji nie wykonujących żadnych działań, tylko w celu przedstawienia typu w określonym wierszu kodu:

function foo(value: string | string[]) {

if (Array.isArray(value)) {

value;

// ^? (parameter) value: string[]

} else {

value;

// ^? (parameter) value: string

}

}

Wiersze value; służą jedynie do zademonstrowania typu w każdym odgałęzieniu instrukcji warunkowej. Nie musimy (i nie powinniśmy) umieszczać tego typu instrukcji w swoim kodzie.

O ile nie napisałem inaczej lub jeśli nie wynika to z kontekstu, przykładowy kod można zweryfikować z flagą --strict.

Wprawdzie drukowane egzemplarze książki się już nie zmienią, sam TypeScript się zmieni i nieuniknione jest, że pewne rodzaje typów lub błędów w przykładach kodu w przyszłości będą odmienne. Warto sprawdzać repozytorium książki na Githubie (https://github.com/danvk/effective-typescript), aby uzyskać zaktualizowane wersje przykładów. Wszystkie próbki kodu zostały zweryfikowane za pomocą literate-ts (https://oreil.ly/LFR0l) w wersji dla TypeScript 5.4.

Typograficzne konwencje stosowane w tej książce

W tej książce stosowane są następujące konwencje typograficzne:

Kursywa

Oznacza nowe terminy, adresy URL, adresy e-mail, nazwy plików i rozszerzenia plików.

Krój o stałej szerokości

Wykorzystywany w listingach oraz w tekście do podkreślenia elementów programu, takich jak nazwy zmiennych lub funkcji, baz danych, typów danych, zmiennych środowiskowych i słów kluczowych.

Pogrubiony krój o stałej szerokości

Prezentuje polecenia lub inny tekst, który użytkownik powinien wpisać.

Krój o stałej szerokości z kursywą

Prezentuje tekst, który czytelnik powinien zastąpić wartością odpowiednią dla jego środowiska lub przez wartości wynikające z kontekstu.

Ten tekst oznacza wskazówkę lub sugestię.

Ten tekst oznacza ogólną uwagę.

Ten tekst oznacza ostrzeżenie.

Używanie przykładowego kodu

Dodatkowe materiały (przykładowy kod, ćwiczenia, itd.) można pobrać ze strony https://github.com/danvk/effective-typescript.

Ta książka ma nam pomóc w wykonaniu swoich zadań. Ogólnie rzecz biorąc, przykładowy kod zawarty w tej książce można wykorzystać w swoich programach i dokumentacji. Nie ma potrzeby wnioskowania o zezwolenie, o ile nie zamierzamy wykorzystać znacznych fragmentów kodu. Przykładowo, jeśli piszemy program zawierający kilka fragmentów kodu z tej książki, nie potrzebujemy pozwolenia. Jeśli zamierzamy sprzedać lub opublikować przykłady z książek wydawnictwa O’Reilly, musimy uzyskać pozwolenie. Jeśli odpowiemy na pytanie cytując tę książkę i przykładowy kod, nie potrzebujemy pozwolenia. Jeśli umieścimy znaczną ilość przykładowego kodu do dokumentacji swojego produktu, musimy uzyskać pozwolenie.

Doceniamy, lecz nie wymagamy podawania źródeł pochodzenia kodu. Zwykle należy przy tym podać tytuł, autora, wydawnictwo oraz numer ISBN oryginalnego wydania angielskiego. Przykładowo: „TypeScript. Skuteczne programowanie wydanie 2 autorstwa Dana Vanderkama (O’Reilly). Copyright 2024 Dan Vanderkam, 978-1-492-05374-3”.

Jeśli ktoś sądzi, że wykorzystanie przykładowego kodu wymaga uzyskania pozwolenia, zachęcamy do kontaktu pod adresem permissions@oreilly.com.

Kontakt z nami

Uwagi i pytania dotyczące tej książki należy kierować na następujący adres wydawnictwa:

O’Reilly Media, Inc.

1005 Gravenstein Highway North

Sebastopol, CA 95472

Pod adresem https://oreil.ly/effective-typescript-2e znajduje się strona WWW poświęcona tej książce. Dostępna jest tu errata, przykłady oraz dodatkowe informacje.

Więcej informacji o naszych książkach, kursach, konferencjach i nowościach można znaleźć na naszej stronie pod adresem http://www.oreilly.com.

Znajdź nas na Facebooku: http://facebook.com/oreilly

Śledź nas na Twitterze: http://twitter.com/oreillymedia

Oglądaj nas na YouTube: http://www.youtube.com/oreillymedia

Podziękowania

Pomimo złudnych nadziei, pisanie drugiego wydania nie okazało się ani łatwiejsze, ani mniej czasochłonne, niż pierwszego. W czasie tego procesu książka rozrosła się z 62 Elementów do 83. Oprócz napisania 22 nowych Elementów (dwa spośród starych Elementów zostały skonsolidowane w jeden nowy) przejrzałem i starannie poprawiłem cały oryginalny tekst. Niektóre części, takie jak Elementy 45 i 55, są praktycznie napisane na nowo.

Wiele nowych Elementów opiera się na materiałach, które najpierw pojawiły się na blogu Effective TypeScript (https://effectivetypescript.com), choć wszystkie zostały znacząco przeredagowane. Rozdział 6 jest w większości oparty na moich osobistych doświadczeniach zdobytych przy budowaniu bibliotek crosswalk (https://oreil.ly/-XQ6A) oraz crudely-typed (https://oreil.ly/E7_gV) dla Delve w Sidewalk Labs.

Oto pochodzenie wszystkich nowych elementów drugiego wydania:

Element 28 jest adaptacją postu na blogu „Use Classes and Currying to Create New Inference Sites” (https://oreil.ly/OAApn).

Element 32 zrodził się z przeglądów kodu. Nie zdawałem sobie sprawy z istnienia tej reguły, dopóki nie ujrzałem jej złamanej!

Element 36 został zainspirowany opiniami przekazywanymi przeze mnie przy niezliczonych przeglądach kodu.

Element 37 opiera się na osobistym doświadczeniu oraz poście na blogu Evana Martina „Why Not Add an Option for That?” (https://oreil.ly/YJWQR). Częste tweety Cory House’a na ten temat dodały mi odwagi, aby dołączyć ten temat do książki.

Element 38 został zainspirowany cytatem z Alana Perlisa, który często powtarzam, a także regułą Scotta Meyersa.

Element 39 opiera się na doświadczeniach mojego zespołu z adapterem Jsonify, który z radością przyjęliśmy, a z jeszcze większą radością porzuciliśmy. To doświadczenie zaowocowało postem na blogu „The Trouble with Jsonify: Unify Types Instead of Modeling Small Differences” (https://oreil.ly/zVod4).

Element 48 został zaadaptowany z postu „The Seven Sources of Unsoundness in TypeScript” (https://oreil.ly/NiTnr), ze znaczącym wkładem Ryana Cavanaugh.

Element 50 został zainspirowany przemyśleniami nad tym, czym typy są naprawdę, oraz odpowiedzią ze StackOverflow wyjaśniającą typy zależne.

Element 51 to adaptacja postu na blogu „The Golden Rule of Generics” (https://oreil.ly/yaxs8), który z kolei jest adaptacją porady z podręcznika TypeScriptu.

Element 53 był inspirowany moją pracą nad bibliotekami crosswalk i crudely-typed oraz ciekowością, jaką wzbudziły we mnie te wszystkie wrappery [T], które tam widziałem.

Element 54 był inspirowany moimi własnymi badaniami na temat szablonowych typów literałów po wydaniu wersji TypeScript 4.1, co znalazło kulminację w poście na blogu „TypeScript Splits the Atom!” (https://oreil.ly/Es6Ep).

Element 56 to ukoronowanie mojego długotrwałego zainteresowania tym tematem. Zaczęło się od odpowiedzi Titian Cernicovej-Dragomir na pytanie na Stack Overflow na temat typowania _.invert, do czego doszły moje własne doświadczenia z bibliotekami crosswalk i crudely-typed, co ostatecznie doprowadziło do postu na blogu „The Display of Types” (https://oreil.ly/bue9Q).

Element 57 był inspirowany informacjami o wydaniu TypeScript 4.5, w którym dodano rekurencję ogonową.

Element 58 był inspirowany doświadczeniami w łączeniu TypeScript z bazami danych, co ostatecznie doprowadziło do mojego wykładu na TypeScript Congress 2022: „TypeScript and SQL: Six Ways to Bridge the Divide” (https://oreil.ly/ofuph).

Element 59 przedstawia szeroko rozpowszechniony trik, z którym zapoznał mnie Jesse Hallett podczas recenzowania pierwszego wydania. Odmiana „par” pochodzi z tweeta Toma Hicksa z 2021 roku.

Element 62 był inspirowany postem na blogu Artsy: „Conditional Types in TypeScript” (https://oreil.ly/r-7E0).

Element 63 pochodzi z uwag Ryana Cavanaugh do pierwszego wydania, które ostatecznie zebrałem w post na blogu: „Exclusive Or and the Optional never Trick” (https://oreil.ly/os01S). Entuzjazm Stefana Baumgartnera dla tego triku wyrażony w jego książce TypeScript Cookbook (O’Reilly) zachęcił mnie do włączenia go do książki.

Element 71 był inspirowany dyskusją z Evanem Martinem na reddicie i frustrującym bugiem, który sprowadzał się do new Set("string"). Doprowadziło to do postu na blogu „In Defense of Interface: Using Declaration Merging to Disable ‘Bad Parts’” (https://oreil.ly/iYGLY).

Element 74 to temat, który pojawia się często, szczególnie gdy nie mamy właściwego modelu myślowego pisania w TypeScript.

Element 76 był inspirowany niezliczonymi sesjami debugowania, które ostatecznie odkrywały niewłaściwy model naszego środowiska.

Element 77 był inspirowany osobistą ciekawością na ten temat, kilkoma pytaniami na Stack Overflow oraz wykładem Gary’ego Bernhardta.

Element 78 wynika z bolesnych osobistych doświadczeń ze spowolnieniem działania TypeScriptu. Opiera się na stronie wiki TypeScriptu oraz poście na blogu „What’s Typescript Compiling? Use a Treemap to Find Out” (https://oreil.ly/QRilV).

Dziękuję moim recenzentom technicznym (kolejność przypadkowa): Josh Goldberg, Stefan Baumgartner, Ryan Cavanaugh, Boris Cherny i Titian Cernicova-Dragomir. Wasze uwagi sprawiły, że ta książka jest zdecydowanie lepsza. Dziękuję moim współpracownikom z zespołu Delve (szczególnie Stephanie Chew, Luda Zhao, Ha Vu i Amanda Meurer) za wszystkie przeglądy kodu i uwzględnienie mojego bezgranicznego entuzjazmu dla TypeScriptu. Dziękuję wszystkim w wydawnictwie O’Reilly, którzy pomogli w powstaniu tej książki: Angela Rufino, Ashley Stussy, Amanda Quinn, Clare Laylock, Sonia Saruba. Dziękuję Chrisowi Mischaikowowi za korektę w ostatniej chwili. Playlista Jazzy Morning w Spotify, zaczynająca się od Beautiful Sunrise Arta Portinga, zapewniała podkład dźwiękowy podczas pisania i edytowania.

Na koniec dziękuję Alex za jej wsparcie: przez pandemię, śluby online i osobiste, zmianę pracy i wielką przeprowadzkę cieszę się, że przynajmniej jedna rzecz pozostała niezmienna!

Przedmowa do pierwszego wydania

Wiosną 2016 odwiedziłem dawnego współpracownika Evana Martina w siedzibie Google w San Francisco i zapytałem, co go ekscytuje. Zadawałem mu to samo pytanie od wielu lat, ponieważ jego odpowiedzi obejmowały szeroki zakres zagadnień, były nieprzewidywalne, lecz zawsze ciekawe: dotyczyły narzędzi do budowania C++, sterowników audio w systemie Linux, krzyżówek online, wtyczek emacs. Tym razem Evan był zainteresowany językiem TypeScript i programem Visual Studio Code.

To było zaskakujące! Słyszałem już o TypeScript, jednak wiedziałem, że został opracowany przez Microsoft i błędnie sądziłem, że był powiązany z .NET. Nie mogłem uwierzyć, że tak oddany użytkownik systemu Linux jak Evan przeszedł na stronę systemu Microsoft.

Wtedy Evan pokazał mi program vscode oraz narzędzie TypeScript Playground, czym natychmiast mnie przekonał. Wszystko działało tak szybko, a narzędzia do edycji kodu bardzo ułatwiały zbudowanie modelu myślowego systemu typowania. Po latach pisania adnotacji typów w komentarzach JSDoc dla narzędzia Closure Compiler, miałem poczucie tworzenia działającego typowanego kodu JavaScript. A firma Microsoft zbudowała wieloplatformowy edytor tekstu na bazie Chromium? Być może warto się nauczyć tego języka i narzędzi.

Ostatnio zacząłem pracę w Sidewalk Labs i pisałem nasz pierwszy kod JavaScript. Mieliśmy jeszcze tak mało kodu, że Evan i ja zdołaliśmy go przekształcić do TypeScript w ciągu kilku dni.

Wciągnęło mnie to. TypeScript jest czymś więcej niż tylko systemem typowania. Oferuje cały pakiet usług językowych, które są szybkie i łatwe w użyciu. W efekcie TypeScript nie tylko zwiększa bezpieczeństwo programów JavaScript, ale sprawia, że programowanie jest znacznie przyjemniejsze!

Brooklyn, NY; październik 2019

Podziękowania do pierwszego wydania

W pisaniu tej książki pomogło mi wiele osób. Dziękuję Evanowi Martinowi za wprowadzenie mnie do świata TypeScript i pokazanie, jak o nim myśleć. Dziękuję Douwe Osinga za skontaktowanie mnie z wydawnictwem O’Reilly i wsparcie w trakcie tego projektu. Brettowi Slatkinowi dziękuję za porady dotyczące struktury oraz za pokazanie mi, że ktoś kogo znam, mógłby napisać książkę z serii Skuteczne programowanie. Dziękuję też Scottowi Meyersowi za opracowanie tego formatu oraz za post „Effective Effective Books” na jego blogu, na którym się wzorowałem.

Dziękuję swoim recenzentom, Rickowi Battagline’owi, Ryanowi Cavanaughowi, Borisowi Cherny’emu, Yakovowi Fainowi, Jesse Hallettowi i Jasonowi Killianowi. Dziękuję wszystkim swoim współpracownikom w Sidewalk, którzy przez lata uczyli się ze mną TypeScript. Dziękuję wszystkim w wydawnictwie O’Reilly, którzy doprowadzili do wydania tej książki: Angeli Rufino, Jennifer Pollock, Deborah Baker, Nickowi Adamsowi i Jasmine Kwityn. Dziękuję też zespołowi grupy TypeScript NYC, Jasonowi, Orcie i Kiryłowi oraz wszystkim mówcom. Wiele elementów napisałem czerpiąc inspirację z wykładów wygłoszonych podczas spotkań, o czym wspominam na poniższej liście:

Inspiracją do napisania Elementu 3⁵ był post na blogu Evana Martina, który wydał się mi szczególnie ciekawy podczas nauki TypeScript.

Element 7 napisałem zainspirowany wykładem Andersa, dotyczącym typowania strukturalnego oraz relacji keyof, który wygłosił na konferencji TSConf 2018, a także wykładem Jessego Halletta, wygłoszonym na spotkaniu grupy TypeScript NYC Meetup w kwietniu 2019.

Zarówno wskazówki Basarata, jak i pomocne odpowiedzi udzielone przez DeeV i GPicazo w portalu Stack Overflow były podstawą do napisania Elementu 9.

Element 10 opiera się na podobnych poradach udzielonych w Elemencie 4 książki Efektywny JavaScript.

Do napisania Elementu 11 zainspirowało mnie masowe zamieszanie wokół tego zagadnienia podczas spotkania grupy TypeScript NYC Meetup w sierpniu 2019.

Element 13 w dużej mierze opiera się na kilku pytaniach dotyczących różnic między type a interface zadanych w serwisie Stack Overflow. Jesse Hallett zasugerował sformułowanie dotyczące rozszerzalności.

Jacob Baskin zachęcał do napisania Elementu 15 i przekazał mi swoje uwagi dotyczące początkowej wersji.

Do napisania Elementu 18 zainspirowałem się kilkoma przykładami kodu przesłanymi do kategorii r/typescript w serwisie Reddit.

Element 24 jest oparty na moich wpisach w serwisie Medium oraz na wykładzie, który wygłosiłem na spotkaniu grupy TypeScript NYC Meetup w październiku 2018.

Element 29 opiera się na popularnej zasadzie stosowanej w języku Haskell („nieprawidłowe stany powinny być niemożliwe do odwzorowania”). Historia lotu Air France 447 została zainspirowana niesamowitym artykułem Jeffa Wise’a z 2011, opublikowanym w Popular Mechanics.

Element 30 dotyczy problemu, z jakim zetknąłem się w przypadku deklaracji typów z biblioteki Mapbox. Jason Killian zasugerował tytuł.

Porada dotycząca nazewnictwa z Elementu 41 jest popularna, lecz to konkretne sformułowanie zostało zainspirowane krótkim artykułem Dana Northa w książce 97 Things Every Programmer Should Know (O’Reilly).

Inspirację do napisania Elementu 64 zaczerpnąłem z wykładu Jasona Killiana na spotkaniu grupy TypeScript NYC Meetup we wrześniu 2017.

Element 25 opiera się na informacjach o wersji TypeScript 2.1. Termin „ewoluujący typ any” nie jest szeroko rozpowszechniony poza samym kompilatorem TypeScript, jednak wydaje mi się, że warto nazwać ten nietypowy wzorzec.

Inspirację do napisania Elementu 46 zaczerpnąłem z wpisu na blogu Jesse’go Halletta.

Element 47 w dużej mierze wykorzystuje wpis Titiana Cernicova Dragomira w zgłoszeniu problemu nr 33128 w repozytorium TypeScript.

Element 49 opiera się na pracy Yorka Yao nad narzędziem type-coverage. Potrzebowałem takiego narzędzia i okazało się, że istnieje!

Element 66 opiera się na wykładzie, który wygłosiłem w grudniu 2017 na spotkaniu grupy TypeScript NYC Meetup.

Element 52 zawdzięcza swoje istnienie wpisowi Davida Sheldricka na blogu Artsy, dotyczącemu typów warunkowych. Dzięki niemu zrozumiałem to zagadnienie.

Do napisania Elementu 70 zainspirował mnie wykład Steve’a Faulknera, czyli southpolesteve, wygłoszony na spotkaniu grupy w lutym 2019.

Element 55 opiera się na moich wpisach w serwisie Medium oraz pracy nad narzędziem typings-checker, która ostatecznie została uwzględniona w narzędziu dtslint.

Element 72 powstał na podstawie wpisu Kat Busch w serwisie Medium, dotyczącym różnych typów wyliczeniowych w TypeScript, a także na podstawie tekstu Borisa Cherny’ego z książki Programming TypeScript (O’Reilly).

Treść Elementu 60 jest wynikiem niezrozumienia tego zagadnienia przeze mnie oraz moich współpracowników. Wyjaśnienie znalazłem w komentarzu autorstwa Andersa do PR 12253 w repozytorium TypeScript.

Dokumentacja MDN jest podstawą Elementu 75.

Rozdział 10 opiera się na moich własnych doświadczeniach podczas migrowania starzejącej się biblioteki dygraphs.

Wiele wpisów na blogach oraz wykładów, na których oparłem tę książkę, znalazłem za pośrednictwem wpisów w kategorii r/typescript w serwisie Reddit. Jestem szczególnie wdzięczny programistom, którzy udostępnili przykładowy kod ułatwiający zrozumienie typowych problemów początkującym użytkownikom TypeScript. Dziękuję Mariusowi Schulzowi za newsletter TypeScript Weekly. Chociaż tylko czasem jest on wysyłany co tydzień, zawsze stanowi doskonałe źródło materiałów i ułatwia poznawanie najnowszych funkcji TypeScript. Dziękuję Andersowi, Danielowi, Ryanowi i całemu zespołowi rozwijającemu TypeScript w firmie Microsoft za rozmowy i porady dotyczące problemów. Większość moich wątpliwości wynikała z niezrozumienia, lecz nic nie jest tak satysfakcjonujące, jak wprowadzenie poprawki przez samego Andersa Hejlsberga od razu po zarejestrowaniu błędu!

Na koniec dziękuję Alex za całe wsparcie podczas tego projektu oraz zrozumienie okazane podczas przepracowanych wakacji, poranków, wieczorów oraz weekendów, które poświęciłem na ukończenie tej książki.

Rozdział 1

Poznajemy TypeScript

Zanim przejdziemy do szczegółów, w tym rozdziale poznamy ogólny zarys języka TypeScript. Jakie rządzą nim zasady i jak należy go postrzegać? Jaki ma związek z językiem JavaScript? Czy jego typy mogą przyjmować wartości null, czy nie? O co chodzi z tym any? Jakieś pomysły?

TypeScript jest dość niezwykłym językiem, ponieważ nie jest uruchamiany w interpreterze (jak Python i Ruby), ani nie kompiluje się do języka niższego poziomu (jak Java i C). Kompiluje się natomiast do innego języka wysokiego poziomu, czyli do kodu JavaScript. Wykonywany jest kod JavaScript, a nie napisany przez nas kod TypeScript. Zatem relacja między TypeScript a JavaScript jest kluczowa, chociaż może też prowadzić do nieporozumień. Jeśli zrozumiemy zasady tej relacji, ułatwimy sobie drogę do efektywnej pracy z językiem TypeScript.

System typowania w TypeScript także niesie ze sobą nietypowe konsekwencje, które powinniśmy poznać. System typowania jest szczegółowo omówiony w kolejnych rozdziałach, natomiast w tym rozdziale poznamy niektóre związane z nim niespodzianki.

Element 1: Relacja między TypeScript a JavaScript

Jeśli od dawna korzystamy z TypeScript, z pewnością zetknęliśmy się ze stwierdzeniem, że „TypeScript jest nadzbiorem JavaScript” lub „TypeScript jest typowanym nadzbiorem JavaScript”. Co to właściwie znaczy? Jak zdefiniować relację między TypeScript a JavaScript? Ze względu na ścisłe połączenie między tymi językami, zrozumienie zachodzących między nimi relacji jest kluczowe w poprawnym korzystaniu z TypeScript.

A jest nadzbiorem B, gdy wszystkie elementy B należą do A. TypeScript jest nadzbiorem JavaScript pod względem składniowym: o ile nasz program JavaScript nie zawiera błędów składniowych, można go uznać także za program TypeScript. Bardzo możliwe, że podczas weryfikacji typów TypeScript zostaną wykryte pewne niedoskonałości kodu. To jednak jest inny problem. TypeScript nadal przetworzy nasz kod i wygeneruje kod JavaScript. (Jest to kolejny kluczowy aspekt tej relacji, który jest szczegółowo omówiony w Elemencie 3).

Pliki TypeScript mają rozszerzenie .ts zamiast rozszerzenia .js stosowanego w plikach JavaScript⁶. Nie oznacza to, że TypeScript jest zupełnie innym językiem! Ponieważ TypeScript jest nadzbiorem JavaScript, kod w plikach .js jest kodem języka TypeScript. Zmiana nazwy pliku main.js na main.ts tego nie zmieni.

Jest to niezwykle przydatne podczas przekształcania istniejącego kodu źródłowego JavaScript na kod TypeScript. Oznacza to, że nie musimy przepisywać kodu do innego języka, aby móc rozpocząć korzystanie z TypeScript i odnieść korzyści z jego stosowania. Inaczej musielibyśmy postąpić, gdybyśmy chcieli przepisać swój kod JavaScript w języku Java. Ten łatwy sposób migracji jest jedną z najlepszych cech języka TypeScript. Znacznie więcej informacji na ten temat zawiera rozdział 10.

Wszystkie programy JavaScript są programami TypeScript, lecz odwrotne stwierdzenie nie jest prawdziwe: istnieją programy TypeScript, które nie są programami JavaScript. Otóż w TypeScript stosuje się dodatkową składnię określającą typy. (Oprócz tego składnia zawiera nieco innych dodatkowych elementów, głównie ze względów historycznych. Patrz Element 73a).

Oto przykład poprawnego programu TypeScript:

function greet(who: string) {

console.log('Hello', who);

}

Jednak po uruchomieniu tego kodu w programie takim jak node, który wymaga składni JavaScript, uzyskamy błąd:

function greet(who: string) {

^

SyntaxError: Unexpected token :

Fragment : string służy do oznaczenia typu i jest elementem składni TypeScript. Jeśli skorzystamy z tego typu elementu, wyjdziemy poza czysty kod JavaScript (patrz rysunek 1-1).

Rysunek 1-1 Cały kod JavaScript jest kodem TypeScript, lecz nie cały kod TypeScript jest kodem JavaScript

Nie oznacza to jednak, że nie możemy wykorzystać cech TypeScript w przetwarzaniu programów napisanych w czystym języku JavaScript. Możemy! Poniższy przykładowy program JavaScript:

let city = 'new york city';

console.log(city.toUppercase());

wygeneruje błąd po uruchomieniu:

TypeError: city.toUppercase is not a function

Ten program nie zawiera oznaczenia typów, lecz podczas weryfikacji typów TypeScript zostanie znaleziony problematyczny kod:

let city = 'new york city';

console.log(city.toUppercase());

// ~~~~~~~~~~~ Property 'toUppercase' does not exist on type

// 'string'. Did you mean 'toUpperCase'?

Nie musieliśmy informować TypeScript, że zmienna city jest typu string: zostało to wywnioskowane na podstawie początkowej wartości. Wnioskowanie typu jest kluczową cechą TypeScript, której poprawne użycie omawia rozdział 3.

Jednym z celów systemu typowania TypeScript jest wykrywanie kodu, który wygeneruje wyjątek w czasie wykonywania bez konieczności uruchomienia kodu. Kiedy ktoś mówi, że TypeScript jest „statycznym” systemem typowania, ma na myśli wspomnianą cechę. Podczas weryfikacji typów nie zawsze uda się znaleźć kod, który rzuci wyjątek, ale przynajmniej podejmuje się taką próbę.

Nawet jeśli nasz kod nie rzuci wyjątku, może zwrócić niezamierzone wyniki. TypeScript próbuje wychwycić również niektóre z podobnych problemów. Poniższy przykładowy program JavaScript:

const states = [

{name: 'Alabama', capital: 'Montgomery'},

{name: 'Alaska', capital: 'Juneau'},

{name: 'Arizona', capital: 'Phoenix'},

// ...

];

for (const state of states) {

console.log(state.capitol);

}

zwróci następujące wyniki:

undefined

undefined

undefined

Ups! Co się stało? Jest to poprawny program JavaScript (a zatem również TypeScript). Ponadto po jego uruchomieniu nie pojawiły się żadne błędy. Lecz z pewnością spodziewaliśmy się innych wyników. Nawet bez dodawania oznaczeń typów narzędzie do sprawdzania typów w TypeScript wychwyci błąd (i zasugeruje rozwiązanie):

for (const state of states) {

console.log(state.capitol);

// ~~~~~~~ Property 'capitol' does not exist on type

// '{ name: string; capital: string; }'.

// Did you mean 'capital'?

}

W istocie mieliśmy na myśli capital przez „a”. Stany i państwa mają stolice (ang. capital, pisane przez „a”), zaś legislatura zasiada w budynku nazywanym po angielsku capitol (przez „o”).

TypeScript może przechwycić błędy, nawet jeśli nie oznaczymy typu. Natomiast jeśli zadbamy o oznaczenia typu, możemy się spodziewać większych korzyści. Otóż oznaczenia typu informują TypeScript o naszych intencjach, dzięki czemu można wychwycić sytuacje, gdy kod nie spełnia naszych zamierzeń. Co by się stało, gdybyśmy odwrócili literówkę capital/capitol z poprzedniego przykładu?

const states = [

{name: 'Alabama', capitol: 'Montgomery'},

{name: 'Alaska', capitol: 'Juneau'},

{name: 'Arizona', capitol: 'Phoenix'},

// ...

];

for (const state of states) {

console.log(state.capital);

// ~~~~~~~ Property 'capital' does not exist on type

// '{ name: string; capitol: string; }'.

// Did you mean 'capitol'?

}

Wcześniej przydatny komunikat błędu teraz jest zupełnie bezużyteczny! Problem polega na tym, że tę samą zmienną zapisaliśmy w dwóch miejscach inaczej, a TypeScript nie wie, który zapis jest poprawny. Może to odgadnąć, ale nie zawsze poprawnie. Rozwiązanie polega na określeniu swoich intencji poprzez jawną deklarację typu zmiennej states:

interface State {

name: string;

capital: string;

}

const states: State[] = [

{name: 'Alabama', capitol: 'Montgomery'},

// ~~~~~~~

{name: 'Alaska', capitol: 'Juneau'},

// ~~~~~~~

{name: 'Arizona', capitol: 'Phoenix'},

// ~~~~~~~ Object literal may only specify known

// properties, but 'capitol' does not exist in type

// 'State'. Did you mean to write 'capital'?

// ...

];

for (const state of states) {

console.log(state.capital);

}

Teraz błędy poprawnie identyfikują problem, a sugerowana poprawka jest właściwa. Sygnalizując swoje intencje ułatwiliśmy TypeScript wychwycenie innego potencjalnego problemu. Gdybyśmy pomylili się w zapisie zmiennej capitol tylko w jednym miejscu tablicy, nie uzyskalibyśmy wcześniej błędu. Jednak po wprowadzeniu oznaczeń typu zauważymy błąd:

const states: State[] = [

{name: 'Alabama', capital: 'Montgomery'},

{name: 'Alaska', capitol: 'Juneau'},

// ~~~ Did you mean to write 'capital'?

{name: 'Arizona', capital: 'Phoenix'},

// ...

];

Możemy dodać do diagramu Venna nową grupę programów: programy TypeScript, pomyślnie zweryfikowane pod kątem poprawności typów (patrz rysunek 1-2).

Rysunek 1-2 Wszystkie programy JavaScript są programami TypeScript. Jednak tylko niektóre programy JavaScript (i TypeScript) przechodzą weryfikację typów

Jeśli ktoś nie zgadza się ze stwierdzeniem, że „TypeScript jest nadzbiorem JavaScript”, być może ma na myśli ten trzeci zbiór programów z diagramu. W praktyce jest to najbardziej odpowiedni sposób codziennego używania języka TypeScript. Zasadniczo podczas pisania programów TypeScript staramy się, aby nasz kod został pomyślnie zweryfikowany pod kątem poprawności typów.

System typowania w TypeScript odzwierciedla zachowanie w czasie wykonywania programu JavaScript. Jeśli wcześniej programowaliśmy w języku o bardziej rygorystycznej weryfikacji typów, to może być dla nas zaskoczeniem. Oto przykład:

const x = 2 + '3'; // OK

// ^? const x: string

const y = '2' + 3; // OK

// ^? const y: string

Obydwie instrukcje zostaną pomyślnie zweryfikowane pod kątem typów, chociaż wydaje się to dyskusyjne i w wielu innych językach doprowadziłoby do błędów w czasie wykonywania. Jednak te przykłady odzwierciedlają zachowanie w czasie wykonywania kodu JavaScript, kiedy to obydwa wyrażenia zostaną przetworzone do łańcucha "23".

TypeScript jednak wyznacza pewną granicę. Podczas weryfikacji typów we wszystkich poniższych instrukcjach zostanie wykryty błąd, chociaż w trakcie działania programu nie zostaną zgłoszone wyjątki:

const a = null + 7; // Evaluates to 7 in JS

// ~~~~ The value 'null' cannot be used here.

const b = [] + 12; // Evaluates to '12' in JS

// ~~~~~~~ Operator '+' cannot be applied to types ...

alert('Hello', 'TypeScript'); // alerts "Hello"

// ~~~~~~~~~~~~ Expected 0-1 arguments, but got 2

Wiodącą zasadą systemu typowania TypeScript jest modelowanie zachowania programu JavaScript w trakcie działania. Jednak TypeScript traktuje dziwną składnię powyższych przykładów jak błąd w intencji programisty, a zatem wykracza poza prostą zasadę modelowania zachowania w czasie wykonywania. Podobny mechanizm zaobserwowaliśmy w przykładzie capital/capitol, w którym program nie wygenerował wyjątku (zwrócił wynik undefined) lecz podczas weryfikacji typów znaleziono błąd.

Jak TypeScript określa, kiedy należy modelować zachowanie w trakcie działania programu JavaScript, a kiedy zastosować szersze podejście? Jest to tak naprawdę kwestia gustu. Decydując się na TypeScript ufamy osądowi jego twórców. Jeśli lubimy sumować wartości null i 7 lub [] i 12, albo wywoływać funkcje z nadmiarem argumentów, być może powinniśmy zrezygnować z TypeScript!

Jeśli nasz program zostanie pozytywnie zweryfikowany pod kątem typów, to czy mogą się w nim pojawić błędy w czasie wykonywania? Odpowiedź brzmi „tak”. Oto przykład:

const names = ['Alice', 'Bob'];

console.log(names[2].toUpperCase());

Po uruchomieniu tego kodu, ujrzymy następujący błąd:

TypeError: Cannot read property 'toUpperCase' of undefined

TypeScript zakłada, że podczas dostępu do tablicy uwzględnimy jej zakres. Ponieważ w tym przypadku nie przestrzegamy tej zasady, pojawi się błąd.

Nieprzechwycone błędy pojawiają się często podczas korzystania z typu any, co omawiamy w Elemencie 5 i bardziej szczegółowo w rozdziale 5.

Pierwotną przyczyną występowania tych wyjątków jest różnica między rzeczywistym typem wartości, a postrzeganiem tego typu przez TypeScript. System typowania, który może zagwarantować dokładność wyznaczania typów statycznych, wydaje się solidny. Jednak system typowania w TypeScript zdecydowanie nie jest tak solidny, ani też nie miał taki być. Więcej sytuacji, w której taka niesolidność może się ujawniać, przedstawia Element 48.

Jeśli solidność jest dla nas istotna, powinniśmy rozważyć inne języki, takie jak Reason, PureScript albo Dart. Chociaż oferują one większe bezpieczeństwo w czasie wykonywania, to jednak wymagają pewnego poświęcenia: potrzeba dużo więcej pracy, aby przekonać ich mechanizmy sprawdzania typów, że nasz kod jest poprawny, a poza tym żaden z tych języków nie jest nadzbiorem JavaScript, a zatem migracja okaże się trudniejsza.

Do zapamiętania

TypeScript jest nadzbiorem JavaScript. Innymi słowy, wszystkie programy JavaScript są programami TypeScript. TypeScript ma dodatkowe elementy składni, a zatem programy TypeScript, ogólnie rzecz biorąc, nie są poprawnymi programami JavaScript.

TypeScript wprowadza system typowania, który modeluje zachowanie w czasie wykonywania programu JavaScript i próbuje znaleźć kod, który w czasie wykonywania zwróci wyjątki.

Kod może zostać pozytywnie zweryfikowany pod kątem poprawności typów, lecz rzucić wyjątek w czasie wykonywania.

Chociaż system typowania TypeScript w dużej mierze modeluje zachowanie JavaScript, niektóre konstrukcje możliwe do zastosowania w JavaScript są zabronione w TypeScript. Przykładem jest wywoływanie funkcji z niewłaściwą liczbą argumentów. Jest to w dużej mierze kwestia gustu.

Oznaczenia typów (inaczej nazywane adnotacjami typów) informują TypeScript o naszych intencjach i pomagają odróżnić kod poprawny od niepoprawnego.