Kodokon kodokon.com

Muster aus der Produktion: Branded Types, satisfies und .d.ts

Wende die Expertenmuster an, die eine echte Codebasis absichern: simulierte Nominalität, Prüfung ohne Verbreiterung und ambiente Deklarationen.

11 Min. · 3 Fragen

Diese Lektion in Kodokon öffnen

Die Typisierung von TypeScript ist strukturell: Zwei Typen mit derselben Form sind austauschbar, eine Nutzer-ID vom Typ string ist also von jedem anderen string nicht zu unterscheiden. Branding simuliert eine nominale Typisierung, indem der Basistyp mit einer Phantom-Eigenschaft geschnitten wird, deren Schlüssel ein unique symbol ist: Kein echter Wert besitzt sie jemals, aber der Compiler hält nun jede Marke auseinander.

TYPESCRIPT
declare const brand: unique symbol;

type Brand<T, Name extends string> =
  T & { readonly [brand]: Name };

type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;

function asUserId(raw: string): UserId {
  return raw as UserId;
}

declare function loadUser(id: UserId): void;

loadUser(asUserId("u_42")); // OK
// loadUser("u_42"); // error: bare string rejected
Simulierte Nominalität, ganz ohne Laufzeitkosten.

Der Operator satisfies (TypeScript 4.9) schließt eine ganz bestimmte Lücke. Eine Annotation : T verbreitert den Typ der Variable auf T und verliert dabei die abgeleiteten Literale; ein as T schaltet einen Teil der Prüfungen ab. satisfies T überprüft, dass der Ausdruck zu T passt, ohne den abgeleiteten Typ anzutasten, der so genau wie möglich bleibt. Es ist das ideale Werkzeug für Konfigurationsobjekte: vollständige Prüfung, Genauigkeit intakt.

TYPESCRIPT
type RouteDef = { path: string; auth?: boolean };

const routes = {
  home: { path: "/" },
  admin: { path: "/admin", auth: true },
} satisfies Record<string, RouteDef>;

routes.admin.auth;
// literal type true: the inferred precision is
// preserved, and an unknown key would be rejected
satisfies: strikte Prüfung ohne Verbreiterung.

Eine .d.ts-Datei enthält ausschließlich ambiente Deklarationen: Sie beschreibt Werte, die zur Laufzeit existieren werden, ohne eine einzige Zeile JavaScript zu erzeugen. Um den globalen Geltungsbereich aus einem Modul heraus zu erweitern, ist der Block declare global Pflicht - und die Spezifikation erlaubt ihn nur in einer Datei, die tatsächlich ein Modul ist, daher das Idiom export {} am Anfang der Datei.

TYPESCRIPT
export {};

declare global {
  interface Window {
    analytics: { track(event: string): void };
  }
}
env.d.ts: Das export {} macht die Datei zu einem Modul.

Für eine JavaScript-Abhängigkeit ohne veröffentlichte Typen erzeugt declare module ein ambientes Modul: Der Compiler verwendet deine Deklaration für jeden Import des Pakets. Achte auf die Reichweite von skipLibCheck: Ist es aktiviert, ignoriert es die Fehler aller .d.ts-Dateien, auch deiner eigenen; überprüfe deine Deklarationen also mit Typtests, die in gewöhnlichen .ts-Dateien liegen.

TYPESCRIPT
declare module "legacy-lib" {
  export interface InitOptions {
    debug?: boolean;
  }
  export function init(options?: InitOptions): void;
}
legacy-lib.d.ts: ein Paket ohne veröffentlichte Typen typisieren.

Wissenscheck

Stelle sicher, dass du die wichtigsten Punkte dieser Lektion behalten hast.

  1. Was ist der Unterschied zwischen satisfies T und einer Annotation : T?
    • satisfies verbreitert den Typ des Ausdrucks auf T
    • satisfies prüft die Konformität zu T und behält dabei den genaueren abgeleiteten Typ
    • satisfies schaltet die Prüfungen ab, wie as
    • Keiner: Die beiden Formen sind gleichwertig
  2. Was enthält zur Laufzeit ein mit Brand<string, ...> gebauter markierter Wert?
    • Eine Zeichenkette samt einer Symbol-Eigenschaft
    • Eine schlichte Zeichenkette: Die Marke existiert nur zur Compile-Zeit
    • Ein Objekt, das die ursprüngliche Zeichenkette umhüllt
  3. Warum fügt man export {} am Anfang einer .d.ts-Datei hinzu, die declare global verwendet?
    • Um die globalen Typen in andere Dateien zu exportieren
    • Um die Datei zu einem Modul zu machen, eine von declare global geforderte Bedingung
    • Um automatisch den Strict Mode zu aktivieren
    • Es ist reine Zierde