Skip to content
← Wszystkie wpisy
5 min czytania Dawid Skłodowski

TypeScript dla rubistów

Programiści Ruby kochają typowanie dynamiczne — więc dlaczego TypeScript jest wart uwagi? Deep-dive o stopniowym typowaniu statycznym dla JavaScriptu, co łapie, gdzie pomaga najbardziej i jak rubista powinien myśleć o typach na froncie.

Programiści Ruby zwykle lubią typowanie dynamiczne — duck typing, elastyczność, brak ceremonii — więc TypeScript może wyglądać jak dokładnie ta biurokracja, dla uniknięcia której opuściliśmy inne języki. Ale TypeScript przeszedł z ciekawostki w niemal-domyślny wybór dla poważnego JavaScriptu w 2019, a powody warto zrozumieć, nawet jeśli (zwłaszcza jeśli) jesteś oddanym rubistą. Front to inne środowisko niż backend Rails, a rachunek typów wychodzi tam inaczej. Oto jak o tym myśleć.

Czym TypeScript właściwie jest

TypeScript to JavaScript z opcjonalną warstwą typów statycznych, kompilowany (przez tsc) do zwykłego JavaScriptu, który działa wszędzie, gdzie JS. Kluczowymi słowami są opcjonalny i stopniowy: każdy poprawny JavaScript jest poprawnym TypeScriptem, więc przyjmujesz typy przyrostowo, dodając je tam, gdzie pomagają, i zostawiając resztę jak jest. Anotujesz, kompilator sprawdza spójność, zanim kod się uruchomi, a potem typy znikają — istnieją tylko w czasie kompilacji:

function total(items: LineItem[]): number {
  return items.reduce((sum, item) => sum + item.price * item.quantity, 0)
}

interface LineItem {
  sku: string
  price: number
  quantity: number
}

Kompilator wie teraz, że total bierze tablicę LineItem i zwraca liczbę, i odrzuci, przed czasem działania, kod, który woła go źle albo sięga po pole, które nie istnieje. To „przed czasem działania” to cała propozycja wartości.

Co łapie i dlaczego ma to większe znaczenie na froncie

Błędy, które TypeScript łapie, to głupie, częste, które trapią specyficznie JavaScript: undefined is not a function, literówka w nazwie właściwości zwracająca undefined i przeciekająca, zła kolejność argumentów, kształt, który się zmienił i popsuł trzy miejsca wywołań. W Ruby te pomyłki też się zdarzają, ale łapiesz je szybko — szybkim, kompleksowym zestawem testów i ciasną pętlą edycja-uruchomienie. Na froncie rachunek jest inny w sposoby sprzyjające typom:

  • „Zestaw testów” to człowiek klikający. Ścieżki kodu frontu są często weryfikowane przez kogoś ćwiczącego UI, nie wyczerpującymi testami automatycznymi. Błąd typu, który testy Ruby by złapały, może trafić do przeglądarki niezauważony.
  • Pętla sprzężenia jest wolniejsza, a powierzchnia ogromna. Literówka w rzadko trafianej gałęzi UI może nie wyjść, aż użytkownik ją znajdzie. Kompilator znajduje ją natychmiast, w edytorze.
  • Refaktoryzacja JavaScriptu jest straszna; refaktoryzacja TypeScriptu jest bezpieczna. Zmień nazwę pola albo kształt funkcji, a kompilator wymieni każde miejsce, które teraz pęka. To największa codzienna wygrana — możesz zmieniać kod z pewnością, której narzędzia nie mogły dać w czystym JS.

Więc instynkt rubisty — „nie potrzebuję typów, mam testy i duck typing” — jest rozsądny na backendzie i słabszy na froncie, gdzie siatka bezpieczeństwa szybkich kompleksowych testów jest zwykle cieńsza, a kosztem błędu w czasie działania jest popsuta strona, którą widzi użytkownik.

Funkcje, które rubista doceni

Kilka idei TypeScriptu mapuje się na rzeczy, które już cenisz:

  • Wnioskowanie typów oznacza mniej ceremonii, niż się obawiasz. Nie anotujesz wszystkiego; TypeScript wnioskuje większość typów. const total = items.reduce(...) jest znane jako liczba bez twojego mówienia. Anotujesz granice — sygnatury funkcji, kształty danych — i pozwalasz wnioskowaniu obsłużyć wnętrze, co powstrzymuje to przed czuciem się jak Java.
  • Interfejsy opisują kształty danych — JSON, który zwraca twoje API, propsy, które bierze komponent — co podwaja się jako dokumentacja, która nie może zwietrzeć, bo kompilator ją egzekwuje.
  • Wsparcie edytora jest transformujące. Ponieważ edytor zna typy, dostajesz dokładne autouzupełnianie, inline’owe podświetlanie błędów i „skok do definicji”, które faktycznie działa. Dla wielu programistów samo to narzędzie jest tego warte — to jak posiadanie wiedzącej pary obserwującej, gdy piszesz.
  • Typy unii i strictNullChecks czynią brak wartości jawnym. string | null zmusza cię do obsłużenia przypadku null, zamieniając najczęstszą awarię frontu — cannot read property of undefined — w błąd kompilacji.

Jak go przyjąć, stopniowo

Właściwym sposobem wniesienia TypeScriptu do projektu jest przyrostowo, co pasuje do jego stopniowej natury:

  • Zacznij z tsconfig.json pobłażliwym, potem zaciskaj. Włącz tryb strict ostatecznie (to tam jest większość wartości, zwłaszcza strictNullChecks), ale możesz zacząć luźno i podkręcać, gdy konwertujesz kod.
  • Typuj granice najpierw — odpowiedzi twojego API, twoje rdzenne modele danych, propsy komponentów — bo to tam złe kształty powodują najwięcej szkód i gdzie typy opłacają się najszybciej.
  • Używaj any jako furtki ucieczki, oszczędnie. any wypisuje wartość z sprawdzania; jest użyteczne do stopniowej migracji, ale każdy any to dziura w siatce bezpieczeństwa, więc traktuj redukowanie ich jako ciągłą pracę.
  • Opieraj się na pakietach @types. Społeczność publikuje definicje typów dla większości bibliotek npm (@types/react itd.), więc dostajesz typowany kod stron trzecich za darmo.

Dla aplikacji Rails z frontem Webpacker/React pasuje to naturalnie: typuj komponenty i klienta API, zostaw prostsze skrypty w spokoju i zaciskaj z czasem.

Uczciwe kompromisy

TypeScript nie jest darmowy. Jest krok kompilacji (już tam, jeśli używasz Webpacka), krzywa uczenia systemu typów (który robi się naprawdę zaawansowany na krawędziach) i trochę ceremonii dla bardzo prostych skryptów, gdzie nic nie zarabia. Osąd, podobnie jak każde inne pytanie „czy dodać tę strukturę” w tych wpisach: wartość TypeScriptu rośnie z rozmiarem bazy kodu, rozmiarem zespołu i tym, jak długo kod musi żyć. Jednorazowy skrypt albo maleńki widżet go nie potrzebuje; duża, długowieczna aplikacja frontu utrzymywana przez zespół to dokładnie tam, gdzie opłaca się wielokrotnie.

Werdykt

Sceptycyzm rubisty wobec typowania statycznego jest dobrze zasłużony na backendzie, gdzie szybkie testy i duck typing dobrze ci służą — ale front to inne środowisko, a argument TypeScriptu jest tam silny. Łapie głupie, częste błędy JavaScriptu przed czasem działania, czyni refaktoryzację bezpieczną zamiast straszną i zamienia twój edytor w wiedzącego asystenta — a jego stopniowy, mocno-wnioskujący projekt oznacza znacznie mniej ceremonii, niż sugeruje słowo „typy”. Przyjmuj go przyrostowo, typuj granice najpierw, włączaj strict, gdy idziesz, i rezerwuj any na migrację. Dla dużego, długowiecznego frontu — czyli większości poważnej pracy frontowej w 2019 — to już nie biurokracja, której rubista się obawia, lecz siatka bezpieczeństwa, której frontowi brakowało.