Wnioskowanie typów to TypeScript ustalający typ, którego nie napisano. Zainicjuj zmienną, a przyjmie typ wartości. Zwróć wartość z funkcji, a typ zwracany wynika z niej sam. Większość kodu w TypeScript ma dużo mniej adnotacji, niż się ludziom wydaje.
Komentarze pokazują, co wyświetla edytor po najechaniu na każdą nazwę. Ostatnia linia to błąd kompilacji (TS2322), mimo że count nigdy nie dostało adnotacji. // @ts-expect-error oznacza go jako oczekiwany, więc reszta bloku się wykonuje.
let a const: poszerzanie literałów
const nigdy się nie zmienia, więc TypeScript daje mu najwęższy typ: samą wartość literału. let dostaje szerszy typ, bo później można mu przypisać coś innego.
let a = "left"; // string
const b = "left"; // "left"
let c = 42; // number
const d = 42; // 42
const e = true; // true
Ma to znaczenie, gdy funkcja przyjmuje tylko określone wartości:
index.ts(7,6): error TS2345: Argument of type 'string' is not assignable to parameter of type '"left" | "right"'.
Popraw to przez const albo przez adnotację zmiennej: let dir: "left" | "right" = "left". Typy literałów mają własną stronę, typy literałów.
Obiekty i tablice poszerzają swoje elementy
Właściwości literału obiektu są modyfikowalne, więc poszerzają się jak let, nawet gdy sam obiekt jest const. Elementy tablic zachowują się tak samo. as const czyni wszystko readonly i zachowuje typy literałów.
Wnioskowanie typu zwracanego
Typ zwracany funkcji jest wnioskowany z jej instrukcji return. Gdy jest ich kilka, wynikiem jest ich unia.
Wywnioskowane typy zwracane wystarczają w większości funkcji. Pisz typ zwracany jawnie w eksportowanych albo publicznych funkcjach, gdy chcesz, żeby sygnatura była kontraktem: zmiana w ciele, która zwraca inny typ, zgłosi wtedy błąd w samej funkcji, a nie u jakiegoś odległego wywołującego.
Typowanie kontekstowe: callbacki dostają typy
Parametry nie są wnioskowane z tego, jak funkcja jest wywoływana. Są dwa wyjątki. Parametr z wartością domyślną przyjmuje typ tej wartości (function retry(times = 3) sprawia, że times to number). A funkcja napisana tam, gdzie już oczekiwany jest typ funkcji, dostaje typy parametrów z tego kontekstu, więc parametry callbacków nie potrzebują adnotacji.
To samo dotyczy forEach, filter, reduce, callbacków Promise, obsługi zdarzeń i każdej funkcji przekazywanej do parametru z zadeklarowanym typem funkcji.
Parametry potrzebują adnotacji
Bez wartości domyślnej albo takiego kontekstu TypeScript nie patrzy na to, jak funkcja jest wywoływana, żeby zgadnąć typy parametrów. Przy włączonym strict parametr bez adnotacji to błąd TS7006:
index.ts(2,17): error TS7006: Parameter 'x' implicitly has an 'any' type.
Napisz function double(x: number), a blok wypisze 42, z typem zwracanym number wywnioskowanym z x * 2.
Najlepszy wspólny typ
Dla literału tablicy z różnymi rodzajami wartości TypeScript wnioskuje tablicę unii ich typów. Nie szuka wspólnego typu bazowego, którego nie wymieniono.
Jedyny przypadek, na który trzeba uważać, to pusta tablica. const list = [] zaczyna jako "ewoluująca" tablica, której typ rośnie z każdym push. Adnotacja const list: string[] = [] od razu mówi, o co chodzi, i jest czytelniejsza.
Kiedy dodać adnotację
| Sytuacja | Adnotacja? |
|---|---|
| Zmienna zainicjowana wartością | Nie, typ wynika z wartości |
Zmienna zadeklarowana bez wartości (let result;) | Tak, let result: string; |
| Parametry funkcji | Tak (poza callbackami z kontekstem albo wartością domyślną) |
| Typ zwracany małej wewnętrznej funkcji | Opcjonalnie |
| Typ zwracany eksportowanej albo publicznej funkcji | Zalecana, jako kontrakt |
| Pusta tablica albo obiekt wypełniany później | Tak, const ids: number[] = [] |
| Wartość ma zachować typy literałów | Użyj as const |
| Wartość musi pasować do typu, ale zachować własny wywnioskowany typ | Użyj satisfies |
Najszybszy sposób, żeby zobaczyć, co zostało wywnioskowane, to najechać na nazwę w edytorze. Bez edytora przypisz wartość do zmiennej o złym typie, na przykład const probe: boolean = mixed;, a komunikat błędu kompilatora poda wywnioskowany typ.
Najczęściej zadawane pytania
Czym jest wnioskowanie typów w TypeScript?
To kompilator ustalający typ, którego nie napisano. let count = 0 daje count typ number, a funkcja, która zwraca a + b dla dwóch liczb, dostaje typ zwracany number. Wywnioskowany typ jest sprawdzany dokładnie tak jak napisany.
Czy w TypeScript trzeba dodawać adnotację do każdej zmiennej?
Nie. Dodawaj adnotacje do parametrów funkcji (nie są wnioskowane z wywołań, tylko z wartości domyślnej albo z kontekstu callbacka), do typów zwracanych eksportowanych funkcji, jeśli chcesz stabilnego API, i do zmiennych zadeklarowanych bez wartości. Resztę niech wywnioskuje TypeScript: const name: string = "Ada" powtarza to, co kompilator już wie.
Dlaczego const wnioskuje typ literału, a let wnioskuje string?
Do const nigdy nie można przypisać nowej wartości, więc const dir = "left" dostaje typ literału "left". let może później przechowywać dowolny inny string, więc let dir = "left" jest poszerzane do string. To samo poszerzanie dotyczy właściwości literału obiektu, bo właściwościom można przypisać nowe wartości. as const to zatrzymuje.
Dlaczego TypeScript mówi, że parametr ma niejawnie typ 'any'?
To błąd TS7006 z noImplicitAny, które należy do strict. TypeScript nie wnioskuje typów parametrów z tego, jak funkcja jest wywoływana, więc goły parametr miałby typ any. Dodaj adnotację, function double(x: number), albo przekaż funkcję tam, gdzie jej typ wynika z kontekstu, na przykład jako callback do map.
Jak zobaczyć typ wywnioskowany przez TypeScript?
Najedź kursorem na nazwę w edytorze (VS Code i większość edytorów pokazuje wywnioskowany typ w dymku). W kodzie przypisanie wartości do zmiennej o oczywiście złym typie sprawia, że kompilator wypisze wywnioskowany typ w komunikacie błędu.