Co nowego w Typescript 5.5?
Siemka! Nie tak dawno oficjalnie wyszedł nowy Typescript, a konkretnie wersja 5.5, która przyniosła kilka naprawdę fajnych ficzerów, które zostały przedstawione w tym poście. Większość z nich dzieje się pod spodem, bez naszej ingerencji, ale uważam, że warto o nich wiedzieć ;) Miłego czytania!
1. Lepsza inferencja typów
Jak widzisz poniżej tworzymy naszą tablicę obiektów trailerParkBoys, do której przypisujemy jakieś dane. Następna w kolejności jest funkcja getTrailerParkBoy, która przyjmuje tablicę stringów names. Następnie na tablicy names robimy operację; mapujemy i filtrujemy wszystkie elementy, które są "undefined", a więc w naszej tablicy boys już nie powinno być żadnego elementu "undefined" prawda?
Inferencja w TS poniżej 5.5
No właśnie w TS poniżej 5.5 (wersja na screenie to 5.4.5) to nie prawda 😕. Widać to w kolejnych linijkach, gdzie próbujemy wywołać metodę "action" na każdym elemencie z tablicy boys, bo dla TS'a element boy może nadal być niezdefiniowany (mimo tego, że wcześniej odfiltrowaliśmy wszystkie niezdefiniowane elementy!).
Poprawa inferencji typów w TS 5.5
Ale tutaj wchodzi Typescript 5.5 cały na biało. 😎 Obecnie Typescript już nie krzyczy o prawdopodobnie niezdefiniowany element, obecnie typ jest poprawnie dedukowany i możemy bez problemu wykonywać metodę boy.action()
Dlaczego to tak?
Typescript 5.5 teraz automatycznie tworzy sobie w metodach takich jak .filter "guarda", czyli sprawdzenie, czy aby napewno element, który mamy na myśli jest odpowiedniego typu, a nie (w tym wypadku) undefined.
Tak jak na screenie poniżej, w zależności od wartości true lub false zachodzącego warunku nasz element jest oczekiwanym typem (w przypadku true) lub czymś innym, w wypadku false; w naszym przykładzie to "undefined".
Wartość true lub false wynika z rezultatu sprawdzenia, które odbywa się pod spodem (i wygląda tak jak zakomentowana funkcja isTrailerParkBoy na screenie poniżej)
const isSpecifiedType(value: WantedType | undefined): value is WantedType => ..
(... is ...Typ oczekuje boolean)
WAŻNE działa tylko gdy bezpośrednio damy "undefined" jako możliwy typ
Warunki kiedy to działa!
- Funkcja nie ma jawnie zdefiniowanego typu return lub bezpośredniego przypisania typu.
- Funkcja zawiera tylko jedną instrukcję return i nie ma ukrytych zwrotów.
- Funkcja nie modyfikuje swojego parametru.
- Funkcja zwraca wyrażenie typu boolean, które jest związane z uściśleniem typu parametru.
Przykład, gdy to nie zadziała:
function getClassroomAverage(students: string[], allScores: Map<string, number>) {
const studentScores = students
.map(student => allScores.get(student))
.filter(score => !!score);
return studentScores.reduce((a, b) => a + b) / studentScores.length;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// error: Object is possibly 'undefined'.
}
Ponieważ warunek !!score w metodzie .filter może być false w przypadku null, undefined (i rzeczywiście niepożądanych wartości), ale tak samo będzie false w momencie gdy score = 0
2. Zawężanie typów (Type Narrowing)
Wiele razy zdarzyło Ci się, że wszystkie znaki na niebie i ziemi wskazywały, że jakiś obiekt NAPEWNO posiada dany klucz, ale tak naprawdę edytor sypał błędy na lewo i prawo krzycząc, że PAAANIE, TU NIE MA TAKIEGO KLUCZA? Mi dosyć często, co sprowadzało się do fikołków w kodzie, dodawania guardów czy rzutowania typów, żeby uświadomić TS'owi jak bardzo się myli. Na szczęście to już tylko historia 😎
TS starszy niż 5.5 nie zawężał poprawnie typu podczas dostawania się do wartości w obiekcie
Jak widać poniżej w Typescript starszym niż 5.5, mimo próby dostania się do wartości rekordu obj po sprawdzeniu, że wartość istnieje i jest stringiem Typescript nadal rzucał błąd Object is of type 'unknown'.
Do tej pory musieliśmy sobie z tym problemem radzić inaczej, na przykład rzutując wartość ręcznie as TYP
TS 5.5 poprawnie dedukuje typ
Jak widać poniżej w Typescript 5.5 zostało to poprawione, dzięki czemu po sprawdzeniu wartości obj[key]
wiemy, że istnieje i jest typu string zatem jesteśmy w stanie się do niej bez problemu dostać za pomocą obj[key]
i wywołać metodę stringa toUpperCase()
3. Sprawdzanie typów w REGEX
Teraz RegExpy są walidowane! To świetna informacja, bo teraz każdy nadmiarowy, bądź urwany nawias będzie nam rzucał błąd. (aaa, z resztą, i tak nikt nie pisze regexpów ręcznie 👀)
4. Importy typów JSDoc
Podejrzewam, że JSDoc nie jest jakoś niesamowicie często używany za sprawą tego, że Typescript sam w sobie "dokumentuje" pisany przez nas kod. Niemniej ja subiektywnie bardzo lubię tego typu "streszczenie" tego, czym dana funkcja, metoda, klasa się zajmuje i za co odpowiada.
Do brzegu; do tej pory byliśmy ograniczeni do typów prymitywnych, przepisywania typu, albo próby importu go z jakichś modułów.
Przed TypeScript 5.5, gdy próbowałeś importować typy z zewnętrznych plików w JSDoc w plikach JavaScript, często pojawiały się błędy w czasie wykonania (runtime). Było to spowodowane tym, że TypeScript traktował te importy jak rzeczywiste importy modułów, co prowadziło do problemów, gdy w rzeczywistości importowane były tylko typy, a nie całe implementacje. Ponadto Typescript za sprawą ograniczonych możliwości statycznego analizowania typów nie mógł dokładnie rozpoznać typów z zewnętrznych plików.
W TS 5.5 możemy użyć słowa kluczowego @import aby zaimportować nasz typ tak jak robimy to normalnie.
5. Zmienna configDir w tsconfigach
Usprawnienie głównie przydatne w projektach typu "monorepo", ale warto o nim wiedzieć. Możemy mieć bazowy plik tsconfig, który współdzielimy w kilku różnych miejscach. No i wszystko spoko, ale co, gdy trzeba na przykład przekompilować TS do danego folderu? Wtedy zaczyna się mały problem, bo choćby nasz "outDir" będzie wypluwał pliki wynikowe do folderu /dist (umownie nazwa dist), ale w miejscu, gdzie znajduje się nasz plik bazowy, a nie ten, który go rozszerza.
Z pomocą przychodzi nam nowa zmienna configDir, która sprawia, że ta ścieżka będzie w odniesieniu do pliku, który rozszerza, a nie bazowego. Czyli w wypadku na screenie będzie kompilować nasze pliki TS do folderu /front-dir/dist (a nie /dist).
Używamy tego w taki sam sposób jak zmienne w template stringu: "${configDir}/nazwa_folderu"
; tak samo jak na screenie poniżej. ;)
Podsumowanie
To są według mnie najważniejsze pozycje, które sprawią, że praca będzie o wiele przyjemniejsza. Zachęcam do wgryzienia się w pozostałe smaczki w tym wydaniu TS'a 🤓
Do następnego! ~ Bartek
Materiały źródłowe: