Const-Assertions für literale, unveränderliche Typen
as const zwingt den Compiler, den engstmöglichen literalen Typ für einen Ausdruck abzuleiten, statt ihn auf einen weiten Typ wie string oder number[] zu verallgemeinern. Das Ergebnis sind präzisere Typen, ganz ohne zusätzliche Typdefinition.
Inhaltsverzeichnis
- 1. Was as const mit einem Ausdruck macht
- 2. Array-Literale mit as const als Tupel statt Array
- 3. Objekt-Literale: readonly Properties und literale Werte
- 4. as const in Kombination mit Union Types statt enum
- 5. as const und satisfies gemeinsam nutzen
- 6. Praxisbeispiel: Konfigurationsobjekte und Action-Typen
- 7. Grenzen von as const: keine Laufzeit-Garantie
- 8. as const, Readonly
und Object.freeze im Vergleich - 9. Typische Fehler beim Einsatz von as const
- 10. Zusammenfassung
- 11. FAQ
1. Was as const mit einem Ausdruck macht
Ohne Const-Assertion leitet TypeScript für ein Literal den weitestmöglichen passenden Typ ab: Aus let status = "active" wird der Typ string, nicht das literale "active". Diese Verbreiterung ist sinnvoll, weil eine let-Variable später neu zugewiesen werden könnte.
Mit as const hinter einem Ausdruck weist man den Compiler an, genau das Gegenteil zu tun: den engstmöglichen, literalen Typ abzuleiten und alle enthaltenen Eigenschaften implizit als readonly zu markieren.
Der Effekt betrifft nicht nur einzelne Werte, sondern rekursiv ganze Ausdrucksbäume: Arrays werden zu readonly Tupeln mit exakten Elementtypen, Objekte erhalten readonly Properties mit literalen statt verallgemeinerten Werten.
let a = "active"; // Typ: string
let b = "active" as const; // Typ: "active"
const status = { code: 200, label: "ok" };
// Typ: { code: number; label: string }
const statusConst = { code: 200, label: "ok" } as const;
// Typ: { readonly code: 200; readonly label: "ok" }
2. Array-Literale mit as const als Tupel statt Array
Ein normales Array-Literal wird zu einem generischen, veränderlichen Array-Typ verallgemeinert, etwa string[] für eine Liste von Farbnamen. Die Reihenfolge und Länge der Elemente geht dabei vollständig verloren.
Mit as const wird aus demselben Literal ein readonly Tupel mit exakten literalen Elementtypen an jeder Position. Das ist besonders nützlich, wenn eine feste Liste erlaubter Werte gleichzeitig als Laufzeitwert und als Typquelle dienen soll.
Über typeof und den Indexzugriff [number] lässt sich aus einem solchen Tupel direkt eine Union aus allen enthaltenen literalen Werten ableiten, ohne die Liste ein zweites Mal als Typ zu wiederholen.
const themes = ["light", "dark", "system"] as const;
// Typ: readonly ["light", "dark", "system"]
type Theme = (typeof themes)[number];
// Typ: "light" | "dark" | "system"
function setTheme(theme: Theme) {
document.documentElement.dataset.theme = theme;
}
3. Objekt-Literale: readonly Properties und literale Werte
Bei Objekten sorgt as const dafür, dass jede Property als readonly markiert wird und ihr Wert, sofern es sich um ein primitives Literal handelt, als exakter literaler Typ statt als verallgemeinerter Typ wie number oder string erscheint.
Verschachtelte Objekte und Arrays innerhalb des Literals werden ebenfalls rekursiv erfasst: Ein Konfigurationsobjekt mit mehreren Ebenen wird komplett unveränderlich typisiert, ohne dass jede Ebene manuell mit Readonly umschlossen werden müsste.
Das eignet sich gut für Konstanten, die als einzige Quelle der Wahrheit für erlaubte Werte, Konfigurationen oder Routing-Tabellen dienen, weil Tippfehler in abgeleiteten Typen sofort als Compile-Fehler sichtbar werden.
4. as const in Kombination mit Union Types statt enum
Viele Teams verwenden mittlerweile ein Objekt mit as const statt eines enum, um eine feste Menge an Werten zu definieren. Der Vorteil: Es entsteht kein zusätzlicher Laufzeit-Konstrukt wie bei numerischen Enums, sondern ein reines Objekt mit literalen Werten.
Aus diesem Objekt lässt sich über typeof und keyof sowohl ein Union-Typ der Schlüssel als auch der Werte ableiten, was in der Praxis flexibler ist als ein klassisches TypeScript-Enum mit seinen eigenen Sonderregeln.
Ein weiterer Vorteil gegenüber String-Enums: Die Werte bleiben normale, unbenannte String-Literale, die sich problemlos mit externen APIs, JSON-Daten oder Datenbank-Werten vergleichen lassen, ohne über einen Enum-Umweg gehen zu müssen.
const OrderStatus = {
Pending: "pending",
Shipped: "shipped",
Delivered: "delivered",
} as const;
type OrderStatus = (typeof OrderStatus)[keyof typeof OrderStatus];
// Typ: "pending" | "shipped" | "delivered"
function isFinal(status: OrderStatus): boolean {
return status === OrderStatus.Delivered;
}
5. as const und satisfies gemeinsam nutzen
as const allein liefert den engstmöglichen Typ, prüft aber nicht, ob dieser Typ zu einer erwarteten Form passt. Genau hier ergänzt der satisfies-Operator: Er validiert einen Ausdruck gegen einen Zieltyp, ohne den abgeleiteten literalen Typ zu verbreitern.
Die Kombination { ... } satisfies Record ist in dieser Reihenfolge nicht gültig, weshalb satisfies typischerweise nach dem Objektliteral steht und as const weggelassen wird, sobald ein satisfies-Constraint bereits die Struktur prüft.
In der Praxis ersetzt satisfies viele Fälle, in denen früher as const allein für Konfigurationsobjekte verwendet wurde, weil zusätzlich ein Fehler auftritt, sobald ein Schlüssel fehlt oder ein Wert nicht zum erwarteten Typ passt.
type RouteConfig = { path: string; auth: boolean };
const routes = {
dashboard: { path: "/dashboard", auth: true },
login: { path: "/login", auth: false },
} satisfies Record<string, RouteConfig>;
// routes.dashboard.path bleibt literal "/dashboard", nicht nur string
6. Praxisbeispiel: Konfigurationsobjekte und Action-Typen
In State-Management-Bibliotheken wie Redux oder in eigenen Reducer-Implementierungen dient as const häufig dazu, Action-Objekte mit einem literalen type-Feld zu erzeugen, das der Compiler für Discriminated Unions im switch-Statement auswerten kann.
Ohne as const würde das Feld type zu string verbreitert, wodurch TypeScript im switch keine erschöpfende Prüfung mehr durchführen und keine spezifischen Payload-Typen pro Fall ableiten könnte.
Auch API-Client-Konfigurationen mit festen Endpunkt-Pfaden profitieren davon: Ein zentrales, mit as const definiertes Objekt dient gleichzeitig als Laufzeitwert für HTTP-Aufrufe und als Typquelle für alle erlaubten Routen.
function increment(amount: number) {
return { type: "increment", amount } as const;
}
function reset() {
return { type: "reset" } as const;
}
type Action = ReturnType<typeof increment> | ReturnType<typeof reset>;
function reducer(state: number, action: Action): number {
switch (action.type) {
case "increment":
return state + action.amount;
case "reset":
return 0;
}
}
7. Grenzen von as const: keine Laufzeit-Garantie
as const ist ein reines Compile-Zeit-Konstrukt. Zur Laufzeit bleibt das Objekt oder Array ein vollkommen normaler, veränderlicher JavaScript-Wert. Nur der Compiler verweigert Zuweisungen an readonly-markierte Properties.
Wer eine echte Laufzeit-Absicherung gegen Mutation benötigt, etwa weil ein Objekt aus nicht-TypeScript-Code heraus verändert werden könnte, muss zusätzlich Object.freeze() einsetzen, das die Mutation zur Laufzeit tatsächlich verhindert oder zumindest im Strict Mode einen Fehler wirft.
In der Praxis reicht die reine Compile-Zeit-Absicherung für die meisten internen Anwendungsfälle aus, weil Mutationsversuche durch eigenen TypeScript-Code ohnehin schon während der Entwicklung als Fehler auffallen, bevor sie in Produktion gelangen.
8. as const, Readonly und Object.freeze im Vergleich
Readonly<T> wandelt einen bestehenden Typ in eine readonly Variante um, wirkt aber nur eine Ebene tief und verbreitert literale Werte nicht automatisch, während as const beides gleichzeitig erledigt, direkt am Ausdruck selbst.
Object.freeze() ist reines Laufzeitverhalten ohne jede Auswirkung auf die von TypeScript inferierten Typen, sofern es nicht mit einem generischen Rückgabetyp kombiniert wird, der die Readonly-Semantik zusätzlich in die Typebene überträgt.
In produktivem Code werden alle drei häufig kombiniert: as const für literale, unveränderliche Konfiguration im Quellcode, Readonly<T> für Funktionsparameter, die Mutation verhindern sollen, und Object.freeze() dort, wo tatsächlich Laufzeitschutz nötig ist.
9. Typische Fehler beim Einsatz von as const
Ein häufiger Fehler ist, as const auf eine Variable anzuwenden, die anschließend noch mit let deklariert wird: Der literale, readonly Typ verhindert dann jede spätere Zuweisung, selbst wenn let das eigentlich erlauben würde.
Ein zweiter Fallstrick betrifft kontextuelle Typisierung: Wird ein mit as const erzeugtes Objekt an eine Funktion übergeben, die einen weiteren, veränderlichen Typ erwartet, kann TypeScript den readonly-Typ zurückweisen, weil eine readonly Struktur nicht automatisch einer veränderlichen Struktur zuweisbar ist.
In diesen Fällen hilft entweder eine explizite Typannotation an der Zielstelle oder der gezielte Verzicht auf as const zugunsten von satisfies, das die Struktur prüft, ohne die spätere Zuweisbarkeit an veränderliche Typen zu blockieren.
| Merkmal | as const | Readonly |
Object.freeze() |
|---|---|---|---|
| Wirkt auf | Ausdruck direkt am Ort der Definition | Bestehenden Typ | Laufzeitobjekt |
| Tiefe | Rekursiv über den gesamten Ausdruck | Nur eine Ebene tief | Nur eine Ebene tief (ohne Zusatzcode) |
| Literale Typen | Ja, verhindert Verbreiterung | Nein, ändert nur Mutierbarkeit | Nein, reine Laufzeitwirkung |
| Laufzeit-Effekt | Keiner | Keiner | Verhindert Mutation tatsächlich |
| Typischer Einsatz | Konstante Konfigurationen, Tupel, Action-Typen | Funktionsparameter, API-Signaturen | Sicherheitskritische, unveränderliche Objekte |
Mironsoft
TypeScript-Migration, Typsicherheit und Team-Onboarding
JavaScript-Codebasis ohne Typsicherheit, aber keine Zeit für eine Rundum-Migration?
Wir migrieren bestehende JavaScript-Projekte schrittweise zu TypeScript, richten strikte Compiler-Einstellungen sauber ein und bringen Teams mit Code-Reviews und Style-Guides auf denselben Typsicherheits-Stand.
Migrations-Fahrplan
Schrittweise JS-zu-TS-Migration ohne Big-Bang-Risiko planen und umsetzen.
Strict-Mode-Einführung
tsconfig.json, ESLint-Regeln und CI-Checks für dauerhafte Typsicherheit aufsetzen.
Team-Onboarding
Entwickler mit Workshops und Code-Reviews in TypeScript-Best-Practices einarbeiten.
10. Zusammenfassung
as const
Literale Typen
Engstmöglicher Typ statt Verbreiterung
Readonly rekursiv
Gilt für verschachtelte Arrays und Objekte
Nur Compile-Zeit
Keine Laufzeit-Garantie gegen Mutation
Enum-Alternative
Objekt plus as const statt enum