Adnotacje typów i inferencja typów w TypeScript

Adnotacje typów w TypeScript to sposób, w jaki programista może jawnie określić, jakiego rodzaju wartości oczekuje w zmiennych, funkcjach, obiektach, tablicach czy klasach. Dzięki adnotacjom typów kompilator TypeScript wie, jakie dane są przypisane do poszczególnych elementów programu, co umożliwia wcześniejsze wykrywanie błędów i zapewnia lepszą kontrolę nad kodem.

Przykłady adnotacji typów dla zmiennych:

const apples: number = 5;
let speed: string = 'fast';
let hasName: boolean = true;
let nothingMuch: null = null;
let nothing: undefined = undefined;

W powyższych przykładach każda zmienna ma przypisany typ (number, string, boolean, null, undefined), co ułatwia przewidywanie, jakie wartości mogą być przechowywane w zmiennych. Takie przypisanie pomaga w uniknięciu błędów typu „niespodziewana wartość” w czasie wykonywania programu.

Adnotacje typów wbudowanych obiektów (np. Date):

let now: Date = new Date();

Zmienna now jest typu Date i oczekuje wartości będącej instancją klasy Date. Dzięki temu mamy pewność, że now będzie przechowywać datę i możemy korzystać z metod dostępnych dla tego typu obiektu, takich jak toLocaleDateString() czy getFullYear().

Definicja i podstawy inferencji typów (Type Inference)

Inferencja typów to proces, w którym TypeScript automatycznie określa typ zmiennej, funkcji czy obiektu na podstawie przypisanej do nich wartości. Jeśli nie użyjemy jawnych adnotacji typów, TypeScript spróbuje samodzielnie odgadnąć, z jakim typem ma do czynienia.

Przykład inferencji typów:

let count = 10; // TypeScript automatycznie przypisze typ `number`
let message = "Hello!"; // TypeScript przypisze typ `string`

W powyższych przypadkach kompilator samodzielnie rozpoznaje typ number i string na podstawie przypisanych wartości. Dzięki inferencji programista nie musi ręcznie przypisywać typów, co sprawia, że kod staje się bardziej przejrzysty.

Różnice między adnotacjami typów a inferencją typów

  • Adnotacje typów (Type Annotations):
    • Są dodawane ręcznie przez programistę.
    • Przydatne w sytuacjach, gdy typ wartości nie jest jednoznaczny lub kiedy chcemy mieć pełną kontrolę nad typami w kodzie.
    • Pomagają w sytuacjach, gdy wartość zmiennej może zmieniać się w zależności od warunków (np. string lub null).
  • Inferencja typów (Type Inference):
    • Działa automatycznie, bez konieczności ręcznego określania typu.
    • Idealna do prostych przypisań, gdy typ jest oczywisty i jednoznaczny.
    • Ułatwia pisanie kodu, zmniejszając ilość deklaracji typów.

Przykłady zastosowania adnotacji i inferencji typów

Adnotacje typów dla zmiennych:

    let speed: string = "fast";
    let age: number = 25;

    Wartości przypisane zmiennym mają jawnie określone typy, co wyraźnie komunikuje ich przeznaczenie.

    Inferencja typów dla zmiennych:

    let color = "blue"; // Kompilator przypisze typ `string`
    let amount = 500;  // Kompilator przypisze typ `number`
    

    Adnotacje typów w funkcjach:

    const logNumber: (i: number) => void = (i: number) => {
      console.log(i);
    };

    Adnotacje określają, że funkcja logNumber przyjmuje argument i typu number i nie zwraca żadnej wartości (void).

    Inferencja typów w funkcjach:

    function greet(name: string) {
      return `Hello, ${name}`;
    }

    TypeScript automatycznie przypisze typ string dla zwracanej wartości, ponieważ name ma typ string.

    Zastosowanie adnotacji typów dla obiektów

    Adnotacje typów są szczególnie przydatne przy definiowaniu struktury obiektów, co zapewnia przejrzystość i jednoznaczność danych:

    let point: { x: number; y: number } = {
      x: 10,
      y: 20
    };
    

    W powyższym przykładzie obiekt point ma zdefiniowane dwie właściwości: x i y typu number.

    Różnica między null a undefined

    null i undefined to specjalne typy używane do oznaczenia braku wartości, ale mają różne znaczenie:

    null:

    • Oznacza celowy brak wartości.
    • Programista musi przypisać null jawnie do zmiennej.
    • Przykład:
      let user: string | null = null;
      
      • Zmienna user może przyjmować wartość string lub null. Użycie null oznacza, że brak wartości jest świadomym wyborem.

      undefined:

      • Oznacza brak przypisanej wartości. Jest to domyślna wartość zmiennej, która została zadeklarowana, ale jeszcze nie została zainicjowana.
      • Przykład:
      let username: string | undefined;
      console.log(username); // undefined
      

      Przykłady użycia null i undefined

      Zmienna z wartością null:

        let currentUser: string | null = "Anna";
        currentUser = null; // Użytkownik wylogowany, brak aktywnego użytkownika
        console.log(currentUser); // Wydrukuje: null

        Zmienna z wartością undefined:

        let pendingUser: string | undefined;
        console.log(pendingUser); // Wydrukuje: undefined
        

        Dlaczego przypisujemy undefined = undefined; null = null?

        Przypisanie undefined do undefined i null do null może wydawać się redundantne, ale ma swoje uzasadnienie:

        1. Jawne określenie typu:
          • Gdy przypisujemy null do zmiennej nothingMuch, jasno komunikujemy, że nothingMuch jest zmienną, która przechowuje wyłącznie null i nie może przechowywać żadnej innej wartości.
          • Przypisanie undefined do undefined wskazuje, że zmienna może przyjmować wartość undefined, co jest przydatne, gdy chcemy pokazać, że zmienna jeszcze nie ma przypisanej wartości, ale oczekujemy jej późniejszego zainicjowania.
        2. Komunikacja w zespole:
          • Jawne przypisanie null lub undefined jest sposobem komunikacji między programistami. Wskazuje, że brak wartości jest zamierzony (null) lub że wartość zmiennej jest jeszcze nieustalona (undefined).
        3. Eliminacja niejasności:
          • Przypisanie null = null lub undefined = undefined eliminuje wszelkie niejasności i pomaga w uniknięciu błędów logicznych, ponieważ wartości te są explicite określone.

        Kiedy używać null, a kiedy undefined?

        Używaj null, gdy:

        Chcesz zasygnalizować, że zmienna powinna być pusta, np. w celu wyczyszczenia wartości:

        let activeUser: string | null = "Jan";
        activeUser = null; // Wylogowanie użytkownika
        

        Używaj undefined, gdy:

        Zmienna jeszcze nie ma przypisanej wartości i oczekujesz jej późniejszego przypisania

        let userRole: string | undefined;
        userRole = "admin"; // Zainicjowanie zmiennej później w kodzie