Ein Literaltyp ist ein Typ mit genau einem Wert: "up" ist ein Typ, dessen einziger Member der String "up" ist, und 404 ist ein Typ, dessen einziger Member die Zahl 404 ist. Allein sind sie nicht sehr nützlich. Zu einer Union verbunden, geben sie dir eine Variable, die eine feste Menge von Werten akzeptiert und sonst nichts.
Ohne den Kommentar @ts-expect-error ist der letzte Aufruf ein Fehler beim Kompilieren (TS2345). Mit ihm kompiliert das Programm, und der Aufruf läuft trotzdem und gibt moving north by 1 aus: Literaltypen existieren nur für den Compiler, und zur Laufzeit ist der Wert ein gewöhnlicher String.
String-, Zahl- und Boolean-Literale
Jeder String, jede Zahl, jeder Bigint und jeder Boolean-Wert lässt sich als Typ schreiben. Der Compiler akzeptiert dann nur genau diesen Wert.
let method: "GET" = "GET";
let code: 404 = 404;
let ok: true = true;
let big: 10n = 10n;
type Port = 80 | 443 | 8080;
const port: Port = 443;
boolean selbst ist nur die Union true | false, deshalb bleibt beim Einengen eines boolean mit if (flag) im else-Zweig false übrig.
| Literaltyp | Erlaubt | Weiterer Typ |
|---|---|---|
"GET" | nur den String "GET" | string |
404 | nur die Zahl 404 | number |
10n | nur den Bigint 10 | bigint |
true | nur true | boolean |
Unions aus Literalen
Der übliche Einsatz ist eine Union, die jeden erlaubten Wert aufführt. In der Funktion engt der Compiler die Union ein, während du sie prüfst, sodass jeder Zweig genau weiß, welchen Wert er hat.
Eine Union aus String-Literalen ist die übliche Alternative zu einem Enum in TypeScript. Sie kostet zur Laufzeit nichts, die Werte sind einfache Strings, die du loggen und per JSON senden kannst, und ein Tippfehler ist ein Compilerfehler. Mehr zu den Abwägungen unter Enums.
Widening: let vs const
Wenn TypeScript einen Typ aus einem Literal ableitet, schaut es, ob sich der Wert ändern kann. Eine const-Variable kann nie neu zugewiesen werden, also behält sie den Literaltyp. Eine let-Variable kann es, also wird ihr Typ zum allgemeinen Typ erweitert.
Fahre im Editor mit der Maus über jeden Namen, um den abgeleiteten Typ zu sehen. Willst du eine let, die nur bestimmte Werte enthält, annotiere sie: let mode: "light" | "dark" = "light".
Warum Objekteigenschaften erweitert werden
Eigenschaften eines Objektliterals sind veränderbar, also werden sie ebenfalls erweitert, selbst wenn das Objekt in einer const liegt. So begegnet man Literaltypen am häufigsten unabsichtlich:
Der Compiler meldet:
index.ts(7,15): error TS2345: Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
req wird als { url: string; method: string } abgeleitet, weil später Code req.method = "DELETE" ausführen könnte. Es gibt drei Lösungen:
Eine vierte Möglichkeit ist satisfies, das das Objekt gegen einen Typ prüft und dabei seine literalen Eigenschaftstypen behält.
as const
as const ist eine Const Assertion. Setze sie hinter einen Ausdruck, und der Compiler leitet den engsten möglichen Typ ab:
- String, Zahl und Boolean-Werte behalten ihre Literaltypen
- Objekteigenschaften werden
readonly - Array-Literale werden zu
readonlyTupeln mit fester Länge
Die Assertion gilt nur beim Kompilieren. Das erzeugte JavaScript ist dasselbe Objektliteral ohne as const, also hindert nichts anderen Code daran, es zur Laufzeit zu verändern. Brauchst du eine Garantie zur Laufzeit, rufe zusätzlich Object.freeze auf.
Ein Union-Typ aus einem as const Array
Ein häufiges Muster: Die erlaubten Werte liegen in einem Array, über das du zur Laufzeit iterieren kannst, und der Union-Typ wird daraus abgeleitet. (typeof arr)[number] bedeutet „der Typ eines beliebigen Elements von arr“.
Ohne as const wäre ROLES ein string[] und Role einfach string. Der Cast zu readonly string[] in isRole ist nötig, weil includes auf einem Tupel aus Literalen nur diese Literale akzeptiert, und der Sinn der Funktion ist, einen String zu prüfen, der vielleicht keines ist. Dasselbe Objektmuster funktioniert für Zuordnungen von Schlüsseln zu Werten: const Status = { Active: "active", Banned: "banned" } as const, dann type Status = (typeof Status)[keyof typeof Status].
const-Typparameter
Eine generische Funktion erweitert normalerweise die Literale, die du ihr übergibst. Seit TypeScript 5.0 kannst du einen Typparameter als const markieren, dann leitet der Compiler das Argument ab, als stünde as const daran, ohne dass der Aufrufer es schreiben muss.
Das ist vor allem ein Werkzeug für Bibliotheksautoren: Routendefinitionen, Builder und Schema-Helfer nutzen es, damit Aufrufer aus einfachen Literalen genaue Typen bekommen.
Die Bedeutungen von const
Das Schlüsselwort const taucht in TypeScript-Code an vier verschiedenen Stellen auf:
| Syntax | Art | Was es tut |
|---|---|---|
const x = 1 | JavaScript-Deklaration | die Bindung kann nicht neu zugewiesen werden; ein Literalwert behält seinen Literaltyp |
expr as const | TypeScript-Assertion | engster Typ: Literale, readonly Eigenschaften, readonly Tupel |
function f<const T>() | TypeScript-Typparameter | leitet Argumente ab, als stünde as const daran |
const enum E {} | TypeScript-Enum | ein Enum, dessen Member beim Kompilieren eingesetzt werden |
Keins davon friert ein Objekt zur Laufzeit ein. const obj = { a: 1 } erlaubt weiterhin obj.a = 2; nur obj selbst neu zuzuweisen ist ein Fehler.
Häufig gestellte Fragen
Was ist ein Literaltyp in TypeScript?
Ein Typ, der genau einen Wert erlaubt. "GET" ist ein Typ, dessen einziger Wert der String "GET" ist, 404 ist ein Typ, dessen einziger Wert die Zahl 404 ist, und true ist ein Typ, dessen einziger Wert true ist. Am nützlichsten sind sie in Unions kombiniert, etwa type Method = "GET" | "POST".
Was macht as const in TypeScript?
as const ist eine Const Assertion. Sie sagt dem Compiler, für einen Ausdruck den engsten Typ abzuleiten: String und Zahlenwerte behalten ihre Literaltypen, Objekteigenschaften werden readonly, und Array-Literale werden zu readonly Tupeln. Sie ändert nur den Typ; der Wert zur Laufzeit ist dasselbe einfache Objekt oder Array und wird nicht eingefroren.
Warum leitet TypeScript string statt meines Literals ab?
Weil der Wert veränderbar ist. let x = "a" und die Eigenschaft in { method: "GET" } können später neu zugewiesen werden, also erweitert TypeScript sie zu string. Eine const-Variable behält den Literaltyp "a". Um Literale in einem Objekt zu behalten, annotiere es mit dem Literaltyp, nutze as const oder satisfies.
Was ist der Unterschied zwischen const und as const?
const ist eine JavaScript-Deklaration: Die Variable kann nicht neu zugewiesen werden, aber das Objekt, auf das sie zeigt, lässt sich weiterhin ändern. as const ist eine TypeScript-Typ-Assertion: Sie macht jede Eigenschaft des Werts im Typsystem readonly und literal. Keins von beiden friert das Objekt zur Laufzeit ein; dafür nimmst du Object.freeze.
Wie bekomme ich einen Union-Typ aus einem Array von Strings?
Deklariere das Array mit as const und greife dann per number auf seinen Typ zu: const roles = ["admin", "user"] as const; type Role = (typeof roles)[number]; ergibt "admin" | "user". Ohne as const ist das Array string[], und das Ergebnis ist nur string.