Typinferenz bedeutet, dass TypeScript einen Typ ermittelt, den du nicht geschrieben hast. Initialisiere eine Variable, und sie übernimmt den Typ des Werts; gib aus einer Funktion einen Wert zurück, und der Rückgabetyp ergibt sich daraus. Der meiste TypeScript-Code enthält weit weniger Annotationen, als viele erwarten.
Die Kommentare zeigen, was ein Editor anzeigt, wenn du mit der Maus über den jeweiligen Namen fährst. Die letzte Zeile ist ein Compilerfehler (TS2322), obwohl count nie annotiert wurde; // @ts-expect-error markiert ihn als erwartet, damit der Rest des Blocks läuft.
let vs const: Literal Widening
Eine const kann sich nie ändern, deshalb gibt TypeScript ihr den engsten Typ: den Literalwert selbst. Eine let bekommt den weiteren Typ, weil sie später neu zugewiesen werden kann.
let a = "left"; // string
const b = "left"; // "left"
let c = 42; // number
const d = 42; // 42
const e = true; // true
Das spielt eine Rolle, wenn eine Funktion nur bestimmte Werte annimmt:
index.ts(7,6): error TS2345: Argument of type 'string' is not assignable to parameter of type '"left" | "right"'.
Die Lösung ist const oder eine Annotation an der Variablen: let dir: "left" | "right" = "left". Literaltypen haben eine eigene Seite, Literaltypen.
Objekte und Arrays erweitern ihre Elemente
Eigenschaften eines Objektliterals sind veränderbar, daher werden sie wie bei let erweitert, selbst wenn das Objekt selbst const ist. Array-Elemente verhalten sich genauso. as const macht alles readonly und behält die Literaltypen.
Inferenz des Rückgabetyps
Der Rückgabetyp einer Funktion wird aus ihren return-Anweisungen abgeleitet. Gibt es mehrere, ist das Ergebnis deren Union.
Abgeleitete Rückgabetypen reichen für die meisten Funktionen. Schreibe den Rückgabetyp bei exportierten oder öffentlichen Funktionen ausdrücklich hin, wenn die Signatur ein Vertrag sein soll: Eine Änderung im Funktionsrumpf, die einen anderen Typ zurückgibt, scheitert dann an der Funktion selbst und nicht bei einem weit entfernten Aufrufer.
Kontextuelle Typisierung: Callbacks bekommen ihre Typen
Parameter werden nicht daraus abgeleitet, wie eine Funktion aufgerufen wird. Es gibt zwei Ausnahmen. Ein Parameter mit Standardwert übernimmt den Typ des Standardwerts (function retry(times = 3) macht times zu einer number). Und eine Funktion, die dort geschrieben wird, wo bereits ein Funktionstyp erwartet wird, bekommt ihre Parametertypen aus diesem Kontext, sodass Callback-Parameter keine Annotation brauchen.
Dasselbe gilt für forEach, filter, reduce, Promise-Callbacks, Event-Handler und jede Funktion, die du an einen Parameter mit deklariertem Funktionstyp übergibst.
Parameter brauchen Annotationen
Ohne Standardwert oder einen solchen Kontext schaut TypeScript nicht nach, wie eine Funktion aufgerufen wird, um ihre Parametertypen zu erraten. Mit strict ist ein nicht annotierter Parameter der Fehler TS7006:
index.ts(2,17): error TS7006: Parameter 'x' implicitly has an 'any' type.
Schreibe function double(x: number), dann gibt der Block 42 aus, und der Rückgabetyp number wird aus x * 2 abgeleitet.
Best Common Type
Bei einem Array-Literal mit unterschiedlichen Arten von Werten leitet TypeScript ein Array der Union ihrer Typen ab. Es sucht nicht nach einem gemeinsamen Basistyp, den du nicht genannt hast.
Das leere Array ist der eine Fall, auf den du achten solltest. const list = [] beginnt als „evolving“ Array, dessen Typ mit jedem push wächst; eine Annotation, const list: string[] = [], nennt die Absicht von vornherein und ist klarer.
Wann annotieren
| Situation | Annotieren? |
|---|---|
| Variable mit einem Wert initialisiert | Nein, der Wert gibt den Typ vor |
Variable ohne Wert deklariert (let result;) | Ja, let result: string; |
| Funktionsparameter | Ja (außer bei Callbacks mit Kontext oder mit Standardwert) |
| Rückgabetyp einer kleinen internen Funktion | Optional |
| Rückgabetyp einer exportierten oder öffentlichen Funktion | Empfohlen, als Vertrag |
| Leeres Array oder Objekt, das du später füllst | Ja, const ids: number[] = [] |
| Wert soll seine Literaltypen behalten | as const verwenden |
| Wert muss zu einem Typ passen, aber seinen eigenen abgeleiteten Typ behalten | satisfies verwenden |
Am schnellsten siehst du den abgeleiteten Typ, indem du im Editor mit der Maus über den Namen fährst. Ohne Editor weist du den Wert einer Variablen eines falschen Typs zu, etwa const probe: boolean = mixed;, und die Fehlermeldung des Compilers nennt den abgeleiteten Typ.
Häufig gestellte Fragen
Was ist Typinferenz in TypeScript?
Der Compiler ermittelt einen Typ, den du nicht geschrieben hast. let count = 0 gibt count den Typ number, und eine Funktion, die a + b aus zwei Zahlen zurückgibt, bekommt den Rückgabetyp number. Der abgeleitete Typ wird genauso geprüft wie ein geschriebener.
Sollte ich in TypeScript jede Variable annotieren?
Nein. Annotiere Funktionsparameter (sie werden nicht aus Aufrufen abgeleitet, nur aus einem Standardwert oder dem Kontext eines Callbacks), die Rückgabetypen exportierter Funktionen, wenn du eine stabile API willst, und Variablen, die ohne Wert deklariert werden. Den Rest lässt du TypeScript ableiten: const name: string = "Ada" wiederholt nur, was der Compiler ohnehin weiß.
Warum leitet const einen Literaltyp ab, let aber string?
Eine const kann nie neu zugewiesen werden, daher bekommt const dir = "left" den Literaltyp "left". Eine let könnte später jeden anderen String enthalten, also wird let dir = "left" zu string erweitert. Dieselbe Erweiterung passiert bei Eigenschaften eines Objektliterals, weil Eigenschaften neu zugewiesen werden können; as const verhindert das.
Warum meldet TypeScript, ein Parameter habe implizit den Typ 'any'?
Das ist der Fehler TS7006 aus noImplicitAny, einem Teil von strict. TypeScript leitet Parametertypen nicht daraus ab, wie eine Funktion aufgerufen wird, also wäre ein nackter Parameter any. Füge eine Annotation hinzu, function double(x: number), oder übergib die Funktion dort, wo ihr Typ aus dem Kontext bekannt ist, etwa als Callback an map.
Wie sehe ich, welchen Typ TypeScript abgeleitet hat?
Fahre im Editor mit der Maus über den Namen (VS Code und die meisten Editoren zeigen den abgeleiteten Typ in einem Tooltip). Im Code kannst du den Wert einer Variablen eines offensichtlich falschen Typs zuweisen; dann nennt der Compiler den abgeleiteten Typ in seiner Fehlermeldung.