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 tekst
  • number trzyma w sobie liczby
  • array lub [] to  tablica, można również deklarować tablice konkretnego typu np.:  string[] lub Cat[]
  • object to powszechnie znany obiekt, wrócimy do niego za kilka akapitów
  • unknown - to typ nieznany
  • any - 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!

Kamila

Dziękuję za poświęcony czas, będzie mi bardzo miło jak zostawisz komentarz :)