12k
All articles

Ein praktischer Leitfaden zum `satisfies`-Operator in TypeScript

TypeScripts satisfies-Operator erklärt mit Config-Beispielen, schmaler Typinferenz und wann er as oder eine Typannotierung ersetzt.

OpenReplay Team
OpenReplay Team
Ein praktischer Leitfaden zum `satisfies`-Operator in TypeScript

Der satisfies-Operator prüft einen Wert gegen einen Typ, ohne den abgeleiteten Typ des Wertes zu verändern — Sie erhalten also gleichzeitig die Typsicherheit des Typs und die Präzision des Wertes. Dieses einzelne Verhalten löst ein konkretes, alltägliches Problem: Eine Doppelpunkt-Annotation auf einem Konfigurationsobjekt schützt zwar vor falschen Werten, verwirft aber die literalen Schlüssel und engen Typen, die Sie eigentlich behalten wollten. Dieser Leitfaden vermittelt Ihnen das mentale Modell, das kanonische Beispiel und eine Entscheidungsregel für die Wahl zwischen satisfies, as und einer einfachen Doppelpunkt-Annotation. Alle Codebeispiele sind für TypeScript 4.9 und höher geschrieben.

Wichtigste Erkenntnisse

  • satisfies validiert einen Wert gegen einen Typ und bewahrt dabei den engen abgeleiteten Typ des Wertes, sodass Autovervollständigung und literale Einschränkung erhalten bleiben.
  • Bei einer Doppelpunkt-Annotation gewinnt der deklarierte Typ und der Wert wird auf diesen erweitert; bei satisfies gewinnt der Wert und der Typ dient nur zur Validierung.
  • as prüft Ihren Wert nicht — es überschreibt den Typprüfer, weshalb const user = {} as User problemlos kompiliert, aber zur Laufzeit einen Fehler wirft, sobald Sie user.name lesen.
  • Annotation und satisfies gleichzeitig zu verwenden (const x: T = {…} satisfies T) ist redundant: Der Doppelpunkt hat Vorrang und hebt die gewünschte Typeinschränkung auf.
  • satisfies ist ausschließlich zur Kompilierzeit wirksam und erzeugt kein JavaScript; verwenden Sie einen Laufzeit-Validator wie Zod, wenn Daten aus einem Netzwerk oder einer Datei stammen.

Das Problem, das satisfies löst

Zwei Muster verleiten Entwickler dazu, satisfies einzusetzen. Das erste ist eine Doppelpunkt-Annotation auf einem Objekt mit Schlüsseln, die den Typ erweitert und die Autovervollständigung zerstört. Matt Pococks routes-Beispiel veranschaulicht dies: Annotiert man mit Record<string, {}>, kann man jeden beliebigen Schlüssel lesen — selbst einen unsinnigen — ohne dass ein Fehler auftritt.

const routes: Record<string, {}> = {
  "/": {},
  "/users": {},
  "/admin/users": {},
};
routes.awdkjanwdkjn; // Kein Fehler — der Typ ist jetzt Record<string, {}>

Das zweite Muster ist eine Eigenschaft mit Union-Typ. Im freeCodeCamp-Beispiel erlaubt eine Eigenschaft, die als Union aus einem String-Literal und einem Objekt typisiert ist, den Aufruf von String-Methoden nicht ohne eine manuelle Typprüfung.

type Info = "John" | "Jack" | { id: number; age: number };
type Person = { myInfo: Info; myOtherInfo: Info };

const applicant: Person = { myInfo: "John", myOtherInfo: { id: 123, age: 22 } };
applicant.myInfo.toUpperCase();
// Property 'toUpperCase' does not exist on type 'Info'

Man muss vor jedem Zugriff if (typeof applicant.myInfo === "string") schreiben. Beide Probleme haben dieselbe Ursache: Die Doppelpunkt-Annotation hat den spezifischen Werttyp durch einen breiteren deklarierten Typ ersetzt.

Was macht der satisfies-Operator?

satisfies prüft, ob ein Ausdruck einem Typ entspricht, ohne den von TypeScript abgeleiteten Typ zu verändern. Er wurde in TypeScript 4.9 eingeführt, veröffentlicht am 15. November 2022, und verhält sich identisch bis zum aktuellen TypeScript 6.0 Stable und dem 7.0 Release Candidate. Da der Go-Port von Version 7.0 die Typrüfungssemantik strukturell identisch zu 6.0 beibehält, erzwingt satisfies auf dem neuen Compiler exakt dieselben Regeln.

Das mentale Modell, in Pococks Formulierung: Bei einer Doppelpunkt-Annotation gewinnt der Typ gegen den Wert; bei satisfies gewinnt der Wert gegen den Typ. Wenn Sie satisfies verwenden, leitet TypeScript den engstmöglichen Typ ab und nutzt die Annotation ausschließlich zur Validierung. Es wirkt ausschließlich zur Kompilierzeit — es erzeugt kein JavaScript und verursacht keine Laufzeitkosten, sodass Tippfehler oder falsche Werttypen erkannt werden, bevor der Code überhaupt ausgeführt wird.

Das kanonische satisfies-Beispiel

Die Lösung besteht darin, die Annotation vom Doppelpunkt zu einem abschließenden satisfies zu verschieben: Beide Probleme verschwinden auf einmal — Sie behalten die engen Literaltypen und erhalten trotzdem einen Fehler bei einem falschen Wert.

const routes = {
  "/": {},
  "/users": {},
  "/admin/users": {},
} satisfies Record<string, {}>;

routes.awdkjanwdkjn;
// Property 'awdkjanwdkjn' does not exist on type
// '{ "/": {}; "/users": {}; "/admin/users": {}; }'

Die Autovervollständigung für routes listet nun die tatsächlichen Pfade auf. Die Validierung bleibt bestehen: Weisen Sie etwas zu, das die Annotation verbietet, lehnt der Compiler es ab.

const routes = {
  "/": null, // Type 'null' is not assignable to type '{}'
} satisfies Record<string, {}>;

Derselbe Ansatz löst den Union-Fall. applicant.myInfo wird auf das Literal "John" eingeschränkt, sodass .toUpperCase() ohne Typprüfung zulässig ist:

const applicant = {
  myInfo: "John",
  myOtherInfo: { id: 123, age: 22 },
} satisfies Person;

applicant.myInfo.toUpperCase(); // OK — abgeleitet als "John"

satisfies vs. as vs. Doppelpunkt-Annotation

as prüft Ihren Wert nicht — es überschreibt den Typprüfer, weshalb const user = {} as User problemlos kompiliert und dann zur Laufzeit einen Fehler wirft, sobald Sie user.name lesen. Das ist der grundlegende Unterschied zwischen den drei Werkzeugen:

WerkzeugPrüft den Wert?Behält enge Ableitung?Kann TS belogen werden?Verwenden wenn
: Type (Doppelpunkt)JaNein — erweitert auf den TypNeinSie bewusst einen breiteren Typ wollen
satisfies TypeJaJaNeinSie Validierung und enge Ableitung wollen
as TypeNeinN/AJaNahezu nie als Standard

Das Laufzeitrisiko ist konkret:

type User = { id: string; name: { first: string; last: string } };
const user = {} as User;
user.name.first; // Kein Fehler in der IDE — wirft zur Laufzeit

as verfault auch still und leise. Dies kompiliert heute problemlos, aber fügen Sie ein Pflichtfeld zu User hinzu, und defaultUser wird ungültig — ohne Fehlermeldung:

type User = { id: string; name: string };
const defaultUser = { id: "123", name: "Matt" } as User;

Dies ist genau die Art von Fehler, für deren Aufdeckung Session Replay entwickelt wurde: Sie sehen die tatsächliche Objektstruktur zum Zeitpunkt des fehlgeschlagenen Property-Zugriffs, nicht die behauptete. Ersetzen Sie as durch satisfies, und der Compiler meldet das fehlende Feld sofort.

Die Faustregel: Greifen Sie nie standardmäßig zu as — verwenden Sie satisfies, um zu validieren und dabei die Typableitung zu bewahren, und nutzen Sie eine einfache Doppelpunkt-Annotation nur dann, wenn Sie bewusst einen breiteren Typ wollen, den Sie später neu zuweisen möchten.

Die Falle der redundanten Annotation

Annotation und satisfies gleichzeitig zu verwenden — const joe: TUser = {…} satisfies TUser — ist redundant: Die Doppelpunkt-Annotation hat Vorrang und hebt die Typeinschränkung, die satisfies bewahren sollte, stillschweigend auf. Der Refine.dev-Artikel zeigt die Konsequenzen — der Zugriff auf eine verschachtelte Eigenschaft schlägt fehl, weil der deklarierte Typ gewonnen hat und die interne Einschränkung verworfen wurde. Entscheiden Sie sich für eines von beiden. Wenn Sie Typeinschränkung wollen, lassen Sie den Doppelpunkt weg.

Wo satisfies seinen Platz verdient

Verwenden Sie satisfies für typisierte Konfigurationen, Record-basierte Maps und Werte mit Discriminated-Union-Typen, die Sie eng eingeschränkt behalten möchten. Theme- und Paletten-Maps sind das Paradebeispiel — das offizielle palette-Beispiel validiert jeden RGB-Eintrag und bewahrt dabei die literalen Typen pro Schlüssel. Für optionale Schlüssel umschließen Sie den Record mit Partial, sodass fehlende Schlüssel erlaubt, vorhandene aber weiterhin geprüft werden:

type Keys = "id" | "name" | "email" | "age";

const person = {
  id: 12345,
  name: "Jacky",
  email: "jacky@test.com",
} satisfies Partial<Record<Keys, string | number>>;

person.name.toUpperCase(); // auf string eingeschränkt

Wann satisfies nicht verwendet werden sollte

Verzichten Sie auf satisfies bei einem einfachen Objekt, bei dem eine schlichte : Type-Annotation bereits alles Notwendige ausdrückt. Verzichten Sie darauf, wenn Sie bewusst den breiteren Typ wollen — wenn Sie eine Variable später neu zuweisen möchten, blockiert satisfies Sie, weil es den engen abgeleiteten Typ festschreibt:

// Doppelpunkt-Annotation — Neuzuweisung ist möglich
let id: string | number = "123";
id = 456; // OK

// satisfies — der Wert gewinnt, also wird auf string eingeschränkt
let id2 = "123" satisfies string | number;
id2 = 456; // Type 'number' is not assignable to type 'string'

Und verzichten Sie vollständig darauf bei Daten, die Sie nicht kontrollieren. satisfies wird nie ausgeführt und kann daher keine JSON-Nutzlast oder Formulareingabe zur Laufzeit validieren — greifen Sie auf einen Laufzeit-Schema-Validator wie Zod oder io-ts zurück, wenn die Daten eine Netzwerk- oder Dateigrenze überschreiten.

Greifen Sie zu satisfies, wann immer Sie ein Literal typisieren und dabei spezifisch halten möchten — Konfigurationen, Routen-Maps, Paletten, Union-Werte. Ersetzen Sie Ihr reflexartiges as damit, behalten Sie Doppelpunkt-Annotationen für die Fälle, in denen ein breiterer Typ der eigentliche Zweck ist, und Ihr nächstes Konfigurationsobjekt wird sowohl Typsicherheit als auch Autovervollständigung bieten.

Häufig gestellte Fragen

Funktioniert der satisfies-Operator in JavaScript-Dateien mit JSDoc?

Ja. TypeScript 5.0 hat einen @satisfies-JSDoc-Tag eingeführt, der in JavaScript-Dateien genau das tut, was der satisfies-Operator in TypeScript-Dateien leistet. In einer JavaScript-Datei schreiben Sie den Tag oberhalb einer Deklaration — beispielsweise eine @satisfies-Annotation mit einem Typnamen — und der Prüfer validiert den Wert gegen diesen Typ, während der enge abgeleitete Typ erhalten bleibt. So können JSDoc-typisierte JavaScript-Projekte denselben Vorteil aus Validierung und Typeinschränkung nutzen, ohne auf .ts-Dateien umsteigen zu müssen.

Verursacht satisfies Laufzeitkosten oder erzeugt es zusätzliches JavaScript?

Nein. satisfies ist ein ausschließlich zur Kompilierzeit wirksamer, typebenenoperator, der kein JavaScript erzeugt — er hat also weder Laufzeitkosten noch Auswirkungen auf die Bundle-Größe. Das Schlüsselwort und der nachfolgende Typ werden bei der Kompilierung gelöscht, genau wie eine Doppelpunkt-Annotation. Da nichts ausgeführt wird, kann es auch keine Daten zur Laufzeit validieren — deshalb benötigen Netzwerk-Payloads oder Formulareingaben weiterhin einen Laufzeit-Schema-Validator wie Zod.

Warum schlägt die Typprüfung meines Objekts fehl, obwohl ich sowohl eine Doppelpunkt-Annotation als auch satisfies verwende?

Weil die Doppelpunkt-Annotation immer Vorrang hat und die satisfies-Klausel zu totem Code wird. Schreibt man const config: Theme = {…} satisfies Theme, gewinnt der deklarierte Theme-Typ, der Wert wird auf Theme erweitert, und die enge Ableitung, die satisfies bewahren sollte, wird verworfen. Der Zugriff auf eine verschachtelte Literaleigenschaft schlägt dann fehl, als wäre satisfies gar nicht vorhanden. Lassen Sie den Doppelpunkt weg und behalten Sie nur das abschließende satisfies, um die Typeinschränkung zu erhalten.

Wann sollte ich Zod statt satisfies zur Datenvalidierung verwenden?

Verwenden Sie Zod oder einen anderen Laufzeit-Schema-Validator wie io-ts, wenn die Daten zur Laufzeit aus einer Quelle stammen, die Sie nicht kontrollieren — etwa einer Netzwerkantwort, einer JSON-Datei oder einer Formulareingabe. satisfies prüft ausschließlich Literale, die Sie in Ihrem Quellcode schreiben, und zwar zur Kompilierzeit; es erzeugt keinen Laufzeitcode und kann daher keine unbekannten eingehenden Daten prüfen. Verwenden Sie satisfies für typisierte Konfigurationen, Record-Maps und Union-Werte in Ihrem eigenen Code; verwenden Sie Zod für externe Schnittstellen.

Open-source session replay

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.