# TypeScript - wprowadzenie

# 1. Typy w JavaScript

Zanim w ogóle przejdziemy do typescriptu powinniśmy odpowiedzieć sobie
czy w zwykłym JavaScripcie mamy typy danych i do czego one są nam potrzebne.

number, string, bigint, boolean, undefined, symbol to 6 prymitywnych
typów, które istnieją w JS'ie. Pozostałe strukturalne dane mają typ object

# 2. Jak typowany jest JavaScript?

JS jest językiem typowanym słabo i dynamicznie - ale co to znaczy i jak wpływa
na pracę developera.

# 2.1 Dynamiczne Typowanie

Zacznijmy do typowania dynamicznego. Dynamiczne typowanie to nic innego jak przypisywanie
typów do wartości zmiennych w trakcie runtime'u.

let foo;
console.log(typeof foo) // undefined
 
foo = 5;
console.log(typeof foo) // number
 
foo = '5'
console.log(typeof foo) // string
1
2
3
4
5
6
7
8

Dzięki takiemu rozwiązaniu nie musimy się martwić o wybranie odpowiedniego typu dla naszych danych...
ale czy na pewno? Dynamicznie typowane dane bardzo łatwo stają się
nieprzewidywalne i powodują wiele błędów, które widoczne są dopiero podczas runtime'u.

# 2.2 Słabe typowanie

Typowanie słabe umożliwia automatyczną konwersję typu jeśli jest to wymagane.

console.log(1 == '1') // true
1

'1' //string jest konwertowany na 1 //number i dopiero potem następuje przyrównanie lewej strony do prawej
Prowadzi to do nieciekawych sytuacji:

let foo = 5;
let bar = '15';
 
foo += bar;
 
console.log(foo) //  '515'
1
2
3
4
5
6

Nie dość, że przy prostym dodawaniu matematyka moze zostać wywalona przez okno to jeszcze zmieniliśmy typ foo z number na string,
teraz poprawne działanie aplikacji stoi pod dużym znakiem zapytania.

# 3. TypeScript na ratunek

Czym jest ten cały TypeScript? To nic innego jak nadzbiór JS'a, dodający między innymi. własne typy,
wyliczeniowy typ danych (enum), statyczne i silne typowanie, który jest transpilowany do natywnego JS'a.
W zasadzie TS można traktować jako alarm, który włącza się za każdym razem gdy próbujemy pisać nieprzewidywalny kod.

# 3.1 Korzyści wynikające z uywania typescriptu

  • zdecydowanie lepsza analiza kodu i podpowiadanie mozliwych do zastosowania metod czy wlasciwości obiektów (razem z ich typami)
  • mozliwość przypisywania typów do danych
  • sprawdzanie poprawności typów danych podczas developmentu pozwala na uniknięcie wielu trudnych do wyśledzenia błędów, które wynikają z nieintuicyjnego zachowania się JS'a
  • uzyskiwanie szczegółowych informacji na temat popełnionych błędów pozwala na ich szybkie poprawienie
  • kazdorazowana kompilacja i analiza kodu pozwala na wyłapanie błedów mogących wystąpić w innych obszarach aplikacji po dokonaniu zmiany pozornie nieistnej dla działania tego obszaru
  • większe bepieczneństwo refaktowowania/wprowadzania zmian w istniejącym kodzie
  • zwiększa poziom rozumienia kodu przez resztę zespołu/dokumentuje kod

# 4. Podstawowe koncepcje typescriptu

# 4.1 Składnia

Składnia typescriptu (dalej TS) jest bardzo podobna do składni javascriptu (dalej JS). Jak zostało wcześniej wspomniane TS jest nadzbiorem JS, co skutkuje tym ze kazdy poprawny zapis czy konstrukcja w JS jest wlaściwą dla TS.

console.log('hau hau, miau miau') // poprawny kod TS
1

TS rozszerza jednak składnie JS o mozliwości dodawania typów dla zmiennych, parametrów funkcji oraz wartości zwracanych przez funkcję. Następuje to poprzez dodanie oczekiwanego dla danej zmiennej czy parametru typu po znaku :

let isPies: boolean // deklaracja zmiennej isPies od type boolean
isPies = true // poprawne przypisanie wartości do wcześniej zadeklarowanej zmiennej, przypisujemy wcześniej zadeklarowany typ
isPies = 'tak' // niepoprawne przypisanie - zadeklarowaliśmy boolean a przypisujemy string - w tym miejscu kompiler TS poinformuje nas o popełnionym błędzie i poda informacje z czego on wynika

const isKot: boolean = false // zadeklarowanie zmiennej o type boolean z natychmiastowym przypisniu jej wartości
1
2
3
4
5
// oczekiwane typy parametrów podajemy definiując funkcję bezpośrednio przy nich
// oczekiwany typ zwracany podajemy po przed blokiem ciała funkcji 
function sum (a: number, b: number): number {
    return a + b
}

// w przypadku funkcji strzałkowej zwracany typ podajemy przed strzałką
const multiply = (a: number, b: number ): number => a * b
1
2
3
4
5
6
7
8

# 4.2 Zapis podstawowych typów danych w TS

Jak zostało juz wyzej wspomniane w TS podajemy typy po znaku :, oto przykłady dla podstawowych typów:

let foo: number;
let isPies: boolean = false;
const bar: string = 'hello world';
 
let tab: Array<number> = [1, 2, 3, 4] // tablica przechowująca wyłącznie numbery
let arr: number[] = [1, 2, 3, 4] // skrócony syntax dla tablicy tablicy
 
let baz: number | string; // do zmiennej baz będziemy mogli przypisać zarówno numer jak i łańcuch znaków
baz = 1 // ok
baz = '1' // ok
baz = false // error

let bazArr: Array<number | string>
bazArr = [1, 2, 3, 4] // ok
bazArr = ['1', '2', '3', '4'] // ok
bazArr = [1, '2'] // ok
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

TS pozwala dodatkowo na uzcycie konstrukcj tuple - struktura danych podobna do tablicy, nie występuje naturalnie w JS; w odróżnieniu od zwyczajnej tablicy tupple ma z góry załozoną ilość elementów oraz ich typ na poszczególnych indexach tablicy

let tuple: [number, string]
tuple = [3, 'kot'] // ok
tuple = ['kot', 3] // error - zadeklarowaliśmy ze pod pierwszym indexem będzie number a nie string
1
2
3

# 4.3 Interfejsy

Intersejsy służą do opisu złożonych struktur danych takich jak obiekty czy metody i funkcje. W przypadku obiektów opisują one dokładnie jakie pola mogą znajdować się obiekcie oraz jakie typy danych można przypisać do tych pól.

interface Person {
    fName: string
    lName: string
}

const person: Person = { fName: 'Michał', lName: 'Nowak' }
1
2
3
4
5
6

Intersejsy opisujące dane w obiektach można zagnieżdżać co pozwala na opisanie bardzo złożonych struktur danych

interface Address {
    street: string
    city: string
}

interface Person {
    fName: string
    lName: string
    address: Address
}

const person: Person = {
    fName: 'Michał',
    lName: 'Nowak',
    address: {
        street: 'Matejki'
        city: 'Poznań'
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Nie zawsze używany przez nas interfejs musi posiadać komplet danych. W takim wypaku należy zastosować opcjonalne właściwości przy definiowaniu intersejsu, definiujemy je przez dodane ? przed :

interface Address {
    street: string
    city: string
}

interface Person {
    fName: string
    lName: string
    addres?: Address
}

const person: Person = {
    fName: 'Michał',
    lName: 'Nowak',
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Przy definiowaniu intersejsów można użyć przy właściwościach słowa kluczowego readonly. Efektem tego jest zablokowanie możliwości zmiany wartości zapisanej pod tym kluczem. Nadal można jednak w nieograniczony sposób odwoływać się do jej wartości.

interface Address {
    street: string
    city: string
}

interface Person {
    readonly fName: string
    readonly lName: string
    addres?: Address
}

const person: Person = {
    fName: 'Michał',
    lName: 'Nowak',
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Za pomocą interfejsu można rówież opisać również dane przyjmowane i zwracane przez funckję

interface AddFunction {
    (num1: number, num2: number): number;
}

function add(num1, num2) {
    return num1 + num2
} as AddFunction
1
2
3
4
5
6
7

# 4.4 Typy any, unknown i void

TypeScript wprowadza dodatkowo typy any, unknown i void.
any to kompletne puszczenie hamulców - możemy robić z nią wszystko co chcemy
tak jak w zwykłym JS'e a TypeErrory znów stają się naszymi przyjaciółmi.

Bezpieczniejszą wersją any jest unknown często nie mamy pewności jaki typ zmiennej
otrzymamy np. z API. Możemy wtedy inicjalizować zmienne z pomocą keywordu unknown.
Gdy tylko do zmiennej przypiszemy wartość TypeScript zapamięta go i nie pozwoli na wszytko jak w przypadku any.

let foo; // any
let bar: unknown;
 
foo = 5;
bar = 5;
 
foo.match() // podczas runtime'u przywita nas: TypeError { foo.match is not a function }
bar.match() // TS od razu da nam znać, że nasz kod zawiera błąd: Property 'match' does not exist on type 'number'.
1
2
3
4
5
6
7
8

void jest to typ odpowiadający wartości zwracanej przez funkcję nie mającej return statement'u.

interface Pies {
    name: string
    isPies: boolean
}

// funkcja nic nie zwraca, jedynie modyfikuje przekazany obiekt - typ zwracany void
function setPiesIsPies(pies: Pies, isPies: boolean): void {
    pies.isPies = isPies
}
1
2
3
4
5
6
7
8
9

# 4.5 Własne typy

TypeScript pozwala na tworzenie własnych typów przy pomocy keyworda type.

type Person = {
  age: number
  name: string
  isPies?: boolean
  }
1
2
3
4
5

Tak stworzony typ możemy wykorzystać wszędzie w naszej aplikacji


let person: Person = { age: 22; name: 'Michal'; }

// zwróćmy uwagę, że mimo tego, że interfejs Person zawiera właściwość 'isPies'
// nie musimy go podawać ponieważ dodaliśmy do niego '?', wszystkie inne właściwości są obligatoryjne.
1
2
3
4
5

Keywordu type możemy używać również jako aliasu dla istniejących już typów jak i unii typów.

type MyString = string;
type MyUnion = string | number;
type MyArray = Array<MyUnion>
1
2
3

# 4.6 Inferencja typów

No dobra, ma to jakiś sens ale czy zawsze musimy pisać typ przy zmiennych?
Nie - TS sam potrafi wywnioskować typ danej na podstawie jej wartości.

let foo = 5;
console.log(typeof foo) // number
 
foo = '5'; // Error Type 'string' is not assignable to type 'number'.
1
2
3
4

Inferencja działa równiez przy zwracanych typach w funkcjach

// TS będzie wiedział ze taka funkcja zwróci number (po dodaniu dwóch numberów nie ma innej opcji)
function add (a: number, b: number) {
    return a + b
}
let sum = add(1, 2) // typ sum to number, TS wywnioskuje to z funkcji add
1
2
3
4
5

# 5. Podsumowanie

Javascript to nasz najlepszy przyjaciel, jak to w przyjaźni bywa - obcując z nim na codzień nie dotrzegamy jego wad. Jednak mimo wszystko je posiada. Typescript jest czymś co te wady niweluje nie zmieniając przy tym załozeń ani logiki lubianego przez nas JS'a. Niewielkim kosztem, uprzejmym poinformowaniem czego dokładnie oczekujemy poprzed dodanie oczekiwanego w danym miejscu typu, mozemy uniknąć kosztownych błędów i kilku siwych włosów na głowie. Nawet nie dodając tyów wprost, poprzez inferencję typescript wyświetli nam stosowne ostrzezenia kiedy będziemy próbować przypisać dane o type niezgodnym z piewotnie załozonymi dla określonych zmiennych.

# Źródła:

  • https://medium.com/@jtomaszewski/why-typescript-is-the-best-way-to-write-front-end-in-2019-feb855f9b164
  • https://www.merixstudio.com/blog/why-use-typescript/