Menu

TypeScript Funktionstyp: Arrow-Syntax und Callback-Typen

Den Typ einer Funktion in TypeScript schreiben: die Arrow-Syntax (a: number) => string, Typaliasse für Funktionen, Arrow Functions und Callbacks typisieren, Call und Construct Signatures und warum der Typ Function zu locker ist.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Ein Funktionstyp beschreibt die Parameter und den Rückgabewert einer Funktion, geschrieben mit einem Pfeil: (a: number, b: number) => number. Gib ihm mit type einen Namen, und du kannst ihn für Variablen, Parameter und Objekteigenschaften verwenden.

Die Funktion, die add zugewiesen wird, braucht keine Annotationen: Ihre Parametertypen kommen aus Operation. Das heißt kontextuelle Typisierung, und es ist der Hauptgrund, Funktionstypen zu benennen.

Syntax von Funktionstypen

Die Form ist (parameters) => ReturnType. Jeder Parameter braucht einen Namen und einen Typ. Die Namen dienen der Lesbarkeit und den Hinweisen im Editor; jede Funktion mit kompatiblen Parametertypen passt, egal wie sie sie nennt.

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

Der Name ist nicht optional. (string) => void deklariert einen Parameter namens string ohne Typ, und der Strict Mode lehnt das ab mit TS7051: Parameter has a name but no type. Did you mean 'arg0: string'?. Schreibe (value: string) => void.

Arrow Functions typisieren

Eine Arrow Function lässt sich auf zwei Arten typisieren. Annotiere die Funktion selbst oder annotiere die Variable mit einem Funktionstyp und lass die Parameter ableiten.

Form 1 ist die übliche Wahl für eine eigenständige Funktion. Form 3 lohnt sich, wenn mehrere Funktionen eine Signatur teilen (Handler, Vergleichsfunktionen, Konverter), weil die Signatur einmal geschrieben wird und eine Änderung daran überall geprüft wird.

Callback-Typen

Ein Parameter, dessen Typ ein Funktionstyp ist, ist ein Callback. Die Funktion des Aufrufers wird dagegen geprüft, und ihre Parameter werden daraus abgeleitet.

Zwei Regeln machen Callbacks angenehm:

  • Weniger Parameter sind in Ordnung. Eine Funktion mit einem Parameter kann übergeben werden, wo zwei angeboten werden. JavaScript ignoriert zusätzliche Argumente, also erlaubt TypeScript das. Mehr Parameter, als der Typ anbietet, sind ein Fehler (Target signature provides too few arguments.).
  • Eine Rückgabe void akzeptiert alles. Ein Callback vom Typ (...) => void darf einen Wert zurückgeben; der Aufrufer verspricht, ihn nicht zu verwenden. Deshalb kompiliert list.forEach((x) => other.push(x)), obwohl push eine Zahl zurückgibt.

Die Parametertypen müssen trotzdem kompatibel sein. (x: string) => ... zu übergeben, wo (a: number, b: number) => number erwartet wird, scheitert mit Types of parameters 'x' and 'a' are incompatible.

Call Signatures

(n: number) => string ist die Kurzform eines Objekttyps mit Call Signature: { (n: number): string }. Die lange Form brauchst du, wenn die Funktion auch Eigenschaften trägt.

TypeScript erlaubt es, einer mit const deklarierten Funktion im selben Scope Eigenschaften hinzuzufügen, und erfasst sie im Typ der Funktion. Auch ein Interface kann eine Call Signature enthalten: interface Counter { (): number; count: number }.

Eine Construct Signature beschreibt etwas, das mit new aufgerufen wird. Sie setzt new davor: new (name: string) => User oder in Objektform { new (name: string): User }. So typisierst du einen Parameter, der eine Klasse bekommt.

Methodensyntax vs Eigenschaftssyntax

In einem Objekttyp lässt sich ein Funktionsmember als Methode schreiben, handle(value: string): void, oder als Eigenschaft, die eine Funktion enthält, handle: (value: string) => void. Sie sehen austauschbar aus, werden unter strict aber unterschiedlich geprüft:

Der Compiler meldet:

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'.

Der Fehler ist berechtigt: onlyStrings kann keine Zahl verarbeiten. Die Methodenform lässt denselben Fehler durch (Methodenparameter werden aus historischen Gründen „bivariant“ geprüft), und a.handle(42) würde zur Laufzeit abstürzen mit TypeError: value.toUpperCase is not a function. Lösche die Zeile mit b, um diesen Absturz zu sehen. Wenn du eigene Objekttypen schreibst, findet die Eigenschaftsform mehr Fehler.

Den Typ Function vermeiden

Function ist der eingebaute Typ, den jeder Funktionswert erfüllt. Er ist fast nie das, was du willst: TypeScript kennt weder die Parameter noch den Rückgabetyp, also wird jeder Aufruf akzeptiert und gibt any zurück.

Ersetze Function durch die echte Signatur. Akzeptierst du wirklich jede Funktion (ein generisches debounce, ein Logging-Wrapper), nimmt (...args: never[]) => unknown jede Funktion an und hält das Ergebnis trotzdem bei unknown statt any. Für Funktionen, deren Typen von ihrer Eingabe abhängen, siehe Generics: Ein generischer Funktionstyp sieht aus wie <T>(value: T) => T.

Häufig gestellte Fragen

Wie definiert man in TypeScript einen Funktionstyp?

Mit der Arrow-Syntax: (a: number, b: number) => number. Gib ihm mit einem Typalias einen Namen, type Compare = (a: number, b: number) => number;, und verwende diesen Namen für Variablen, Parameter und Eigenschaften. Die Parameternamen gehören zur Syntax, dienen aber nur der Dokumentation: Eine Funktion mit anderen Parameternamen passt trotzdem.

Wie typisiere ich in TypeScript einen Callback-Parameter?

Schreibe den Funktionstyp als Typ des Parameters: function onEach(items: string[], cb: (item: string, index: number) => void) { ... }. Aufrufer können eine Funktion mit weniger Parametern übergeben, und die Parameter des Callbacks werden aus diesem Typ abgeleitet, sodass onEach(list, (item) => ...) keine Annotation braucht.

Warum sollte ich in TypeScript den Typ Function nicht verwenden?

Function akzeptiert jede Funktion, und ihr Aufruf wird nicht geprüft: Beliebige Argumente sind erlaubt, und das Ergebnis ist any. Schreibe stattdessen die echte Signatur, etwa () => void oder (value: string) => number, oder (...args: never[]) => unknown, wenn du wirklich jede Funktion akzeptierst.

Was ist der Unterschied zwischen einem Funktionstyp und einer Call Signature?

Sie beschreiben dasselbe. (n: number) => string ist die Kurzform des Objekttyps { (n: number): string }. Die Form mit Call Signature brauchst du, wenn die Funktion auch Eigenschaften hat, zum Beispiel { (n: number): string; label: string }.

Wie typisiere ich in TypeScript eine Arrow Function?

Entweder annotierst du Parameter und Rückgabetyp inline, const half = (n: number): number => n / 2;, oder du gibst der Variablen einen Funktionstyp und lässt die Parameter ableiten: const half: (n: number) => number = (n) => n / 2;.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S