Podstawy TypeScript #2 - typowanie dla zielonych
Szczęśliwego Nowego Roku! Mam nadzieje, że przerwa świąteczno-noworoczna była okazją do odpoczynku. Wróciłam z zdwojoną siła i entuzjazmem!
Serdecznie zapraszam do czytania drugiej części tego cyklu. Dzisiaj będziemy kontynuować podstawy i zjemy duży kawałek TypeScriptowego tortu. Największą zaletą TypeScripta jest... typowanie, więc bez tego trudno ruszyć dalej.
Do przeczytania tego artykuły wymagana jest podstawowa znajomość JavaScript. Nie omówiłam wszystkich typów ani niuansów związanych z typowaniem. Żeby nie było zbyt słodko :)
Typy zmiennych
Na początek drobna uwaga: w TypeScripcie typy natywne zapisujemy z małej litery. Trzeba pamiętać, że number
to typ, a Number
to obiekt.
Tutaj wylistowałam niekompletną liczbę typów. Dlaczego niekompletną? Bowiem trudno mi było jednoznacznie oddzielić wiedzę podstawową od tej bardziej zaawansowanej i zrobiłam to według własnego uznania.
string
to powszechnie znany łańcuch znakowy, przechowuje tekstnumber
trzyma w sobie liczbyarray
lub[]
to tablica, można również deklarować tablice konkretnego typu np.:string[]
lubCat[]
object
to powszechnie znany obiekt, wrócimy do niego za kilka akapitówunknown
- to typ nieznanyany
- to dowolny typ[key | string]
- typy indexów-
<T>
- typy generyczne zostawiam na inny wpis never
void
enum
- zapraszam do czytania specjalnego wpisu o Enumach w TypeScripcie https://solutionchaser.com/typescript-enum/- inne: typy mieszane i zaawansowane
Any i unknown
Z pewnością ten akapit wzbudzi wiele kontrowersji, bowiem samo istnienie typu - nazwijmy to tymczasowo - nieokreślonego wydaje się kontrowersyjne. TypeScript jest po to, żeby typować, więc o co chodzi? Używać czy nie używać? A jeśli tak to kiedy?
Sama koncepcja pochodzi z języków obiektowych, gdzie istnieje bazowy `Object` z którego dziedziczy wszystko. Jak nie ma na co rzutować to zawsze można na Object
.
W JavaScripcie any
/ unknown
pełni podobną rolę dodatkowo ułatwiając przetwarzanie danych, szczególnie tych pobranych z zewnętrznych źródeł. Jeśli jednak uważnie przeczytacie ten post możecie znaleźć lepsze alternatywy.
Moim zdaniem powszechnym błędem jest stosowanie any
oraz unknown
zamiennie lub stosowanie tylko any
.
Any
ma większy zakres, zmienną tego typu można zawołać, może być także konstruktorem. Wszystko można przypisać do zmiennej typu any
i zmienna typu any
może być przypisana do wszystkiego (z drobnym wyjątkiem: never
).
Unknown
został wprowadzony jako poprawka do any
. Do tego typu zmiennej można przypisać wszystko, ale zmiennej typu unknown
nie można przypisać do innego typu niż any
or unknown
. Nie można jej wywołać.
To jakby powiedzieć: "Ok, rozumiem, że w tamtej chwili nie było możliwości, żeby znać strukturę danych, ale żeby ich użyć bezpiecznie musisz rzutować na odpowiedni typ."
// data to dane z nieznanego nam API
let jakakolwiek: any = data;
let nieznana: unknown = data;
let numerek: string = jakakolwiek; // OK
let numerek2: string = nieznana; // ERROR
Ok, to jak to zrobić? Trzeba rzutować, a co za tym idzie: dokładnie wiedzieć co się robi.
let numerek2: string = nieznana as string; // OK
Object, typy indexów i index signatures
Podczas gdy w Javascriptcie obiekty są wolnym duchem, TypeScript lubi trzymać je pod kontrolą.
let obj: { kot: string };
obj = {
kot: 'franek"
}
Dlatego powyższy zapis jest poprawny, ale poniższy wyrzuci błąd:
let obj: { kot: string };
obj = {
kot: 'franek',
pies: 'łapka' // ERROR
}
Czy straciliśmy całą elastyczność związaną z obiektami? Niekoniecznie!
Mamy dwa wyjścia:
- właściwość nieobowiązkowa oznaczona znakiem zapytania
W poniższym przykładzie mamy definicję typu, który jest obiektem z obowiązkową właściwością kot
i dodatkową pies
. Dzięki temu możemy elastyczniej przypisywać obiekty.
let obj: { kot: string, pies?: string };
obj = { // OK
kot: 'franek',
pies: 'łapka'
}
obj = { // OK
kot: 'franek',
}
obj = { // ERROR
pies: 'łapka'
}
Nie będę dzisiaj tego omawiać, ale tutaj również rozwiązaniem mogą być typy zaawansowane/mieszane.
- zdefiniować klucz obiektu
Tutaj mamy mnóstwo władzy, której łatwo naużyć, ale tak: mamy możliwość zdefiniować typu klucza oraz umieszczonych pod nim danych.
let obj: {
[key: string] : string
}
obj = {
piesek: 'łapka',
kotek: 'mruczek',
papuzka: 'swiergotka'
wiek: 3 // ERROR
}
Świetne, ale co z sytuacją gdy chcemy przypisać cokolwiek?
let obj: {
[key: string] : any
}
obj = {
piesek: 'łapka',
kotek: 'mruczek',
papuzka: 'swiergotka',
wiek: 3 // OK
}
Jeśli już potrzebujemy sporej elastyczności, powyższe rozwiązanie jest dobre. Oczywiście klucz może być także number
.
Never & Void
Te typy nie są powiązane, ale bardzo łatwo można je zmylić.
Void
jest typem, który często zwracamy w metodach, bowiem jest on oznaczeniem, że ta funkcja nic nie zwróci. Podczas wykorzystywania tak oznaczonej funkcji linter przypilnuje, żeby nic nie zwracała oraz żebyśmy nie przypisywali wyniku do jakieś zmiennej.
Never
to informacja o tym, że ta funkcja nigdy się nie zakończy. Istnieje kilka takich funkcji np.: niekończący się while.
const immortality = (): never => {
while(1) {
breath();
}
}
Albo funkcja, która zawsze zwróci wyjątek. Są to często funkcje pomocnicze, gdy aplikacja wymaga bardziej skomplikowanej obsługi błędów:
function error(message: string): never {
throw new Error(message);
}
Podsumowanie
Podobało się? To tylko zalążek tego co można zrobić z typami w TypeScriptcie! Wrócimy jeszcze do typów generycznych oraz bardziej zaawansowanego typowania, ale na dzisiaj odpocznijcie. Ten rok będzie szalony!
Zapraszam do komentowania, pewnie kilka niuansów przeoczyłam!