Typ funkcji opisuje parametry i wartość zwracaną funkcji i zapisuje się go ze strzałką: (a: number, b: number) => number. Nadaj mu nazwę przez type, a możesz go używać dla zmiennych, parametrów i właściwości obiektów.
Funkcja przypisana do add nie potrzebuje adnotacji: typy jej parametrów pochodzą z Operation. Nazywa się to typowaniem kontekstowym i jest głównym powodem, by nadawać nazwy typom funkcji.
Składnia typu funkcji
Kształt to (parameters) => ReturnType. Każdy parametr potrzebuje nazwy i typu. Nazwy służą czytelności i podpowiedziom w edytorze; pasuje każda funkcja o zgodnych typach parametrów, niezależnie od tego, jak je nazywa.
type Predicate = (value: number) => boolean;
type Formatter = (value: number, digits?: number) => string; // optional parameter
type Logger = (...parts: string[]) => void; // rest parameter
type Factory = () => { id: number }; // no parameters
Nazwa nie jest opcjonalna. (string) => void deklaruje parametr o nazwie string bez typu, a tryb strict odrzuca to z TS7051: Parameter has a name but no type. Did you mean 'arg0: string'?. Napisz (value: string) => void.
Typowanie funkcji strzałkowych
Funkcję strzałkową można otypować na dwa sposoby. Dodaj adnotacje do samej funkcji albo nadaj zmiennej typ funkcji i pozwól wywnioskować parametry.
Forma 1 to zwykły wybór dla samodzielnej funkcji. Forma 3 opłaca się, gdy kilka funkcji ma wspólną sygnaturę (handlery, komparatory, konwertery), bo sygnaturę pisze się raz, a jej zmiana jest sprawdzana wszędzie.
Typy callbacków
Parametr, którego typem jest typ funkcji, to callback. Funkcja przekazana przez wywołującego jest z nim porównywana, a jej parametry są z niego wnioskowane.
Dwie reguły sprawiają, że callbacków używa się wygodnie:
- Mniej parametrów jest w porządku. Funkcję z jednym parametrem można przekazać tam, gdzie oferowane są dwa. JavaScript ignoruje nadmiarowe argumenty, więc TypeScript na to pozwala. Więcej parametrów, niż oferuje typ, to błąd (
Target signature provides too few arguments.). - Zwracany
voidprzyjmuje wszystko. Callback o typie(...) => voidmoże zwrócić wartość; wywołujący obiecuje jej nie używać. Dlategolist.forEach((x) => other.push(x))się kompiluje, choćpushzwraca liczbę.
Typy parametrów nadal muszą być zgodne. Przekazanie (x: string) => ... tam, gdzie oczekiwane jest (a: number, b: number) => number, kończy się błędem Types of parameters 'x' and 'a' are incompatible.
Sygnatury wywołania
(n: number) => string to skrót od typu obiektowego z sygnaturą wywołania: { (n: number): string }. Dłuższa forma jest potrzebna, gdy funkcja ma też właściwości.
TypeScript pozwala dodawać właściwości do funkcji zadeklarowanej przez const w tym samym zakresie i śledzi je w typie funkcji. Interfejs też może zawierać sygnaturę wywołania: interface Counter { (): number; count: number }.
Sygnatura konstrukcji opisuje coś, co wywołuje się przez new. Dodaje new na początku: new (name: string) => User albo { new (name: string): User } w formie obiektowej. Tak typuje się parametr, który przyjmuje klasę.
Składnia metody a składnia właściwości
W typie obiektowym składową funkcyjną można zapisać jako metodę, handle(value: string): void, albo jako właściwość przechowującą funkcję, handle: (value: string) => void. Wyglądają na wymienne, ale przy strict są sprawdzane inaczej:
Kompilator zgłasza:
index.ts(8,27): error TS2322: Type '(value: string) => void' is not assignable to type '(value: string | number) => void'.
Types of parameters 'value' and 'value' are incompatible.
Type 'string | number' is not assignable to type 'string'.
Type 'number' is not assignable to type 'string'.
Błąd jest słuszny: onlyStrings nie obsłuży liczby. Forma metody przepuszcza ten sam błąd (parametry metod są z powodów historycznych sprawdzane „biwariantnie”), a a.handle(42) wysypałoby się w czasie wykonania z TypeError: value.toUpperCase is not a function. Usuń linię z b, aby zobaczyć ten błąd. Gdy piszesz własne typy obiektowe, forma właściwości wyłapuje więcej.
Unikaj typu Function
Function to wbudowany typ, który spełnia każda wartość funkcyjna. Prawie nigdy nie jest tym, czego chcesz: TypeScript nie zna parametrów ani typu zwracanego, więc każde wywołanie jest akceptowane i zwraca any.
Zastąp Function prawdziwą sygnaturą. Gdy naprawdę przyjmujesz dowolną funkcję (generyczne debounce, wrapper do logowania), (...args: never[]) => unknown przyjmuje każdą funkcję i nadal zostawia wynik jako unknown zamiast any. Funkcje, których typy zależą od wejścia, opisuje strona o generykach: generyczny typ funkcji wygląda tak: <T>(value: T) => T.
Najczęściej zadawane pytania
Jak zdefiniować typ funkcji w TypeScript?
Użyj składni strzałkowej: (a: number, b: number) => number. Nadaj mu nazwę aliasem typu, type Compare = (a: number, b: number) => number;, i używaj tej nazwy dla zmiennych, parametrów i właściwości. Nazwy parametrów są częścią składni, ale służą tylko dokumentacji: funkcja z innymi nazwami parametrów nadal pasuje.
Jak otypować parametr callback w TypeScript?
Zapisz typ funkcji jako typ parametru: function onEach(items: string[], cb: (item: string, index: number) => void) { ... }. Wywołujący może przekazać funkcję z mniejszą liczbą parametrów, a parametry samego callbacku są wnioskowane z tego typu, więc onEach(list, (item) => ...) nie potrzebuje adnotacji.
Dlaczego nie używać typu Function w TypeScript?
Function przyjmuje dowolną funkcję, a jej wywołanie nie jest sprawdzane: dozwolone są dowolne argumenty, a wynik ma typ any. Zamiast tego zapisz prawdziwą sygnaturę, na przykład () => void albo (value: string) => number, lub (...args: never[]) => unknown, gdy naprawdę przyjmujesz każdą funkcję.
Czym różni się typ funkcji od sygnatury wywołania?
Opisują to samo. (n: number) => string to skrót od typu obiektowego { (n: number): string }. Forma z sygnaturą wywołania jest potrzebna, gdy funkcja ma też właściwości, na przykład { (n: number): string; label: string }.
Jak otypować funkcję strzałkową w TypeScript?
Albo dodaj adnotacje parametrów i typu zwracanego bezpośrednio, const half = (n: number): number => n / 2;, albo nadaj zmiennej typ funkcji i pozwól wywnioskować parametry: const half: (n: number) => number = (n) => n / 2;.