Kodokon kodokon.com

Narrowing: typeof, in, instanceof und Type Guards

Du beherrschst die Mechanismen der Typverengung und schreibst eigene Prädikate, um Unions verlässlicher zu machen.

10 Min. · 3 Fragen

Diese Lektion in Kodokon öffnen

Narrowing ist der Mechanismus, mit dem TypeScript einen weiten Typ (eine Union, unknown) durch die Analyse des Kontrollflusses zu einem präziseren verfeinert. Du nutzt ihn bereits, ohne darüber nachzudenken: Jedes if auf einem typisierten Wert löst eine Analyse aus. Der Compiler kennt drei Familien eingebauter Guards: typeof für primitive Werte, instanceof für Klasseninstanzen und in für das Vorhandensein einer Eigenschaft. Wählst du sie gut, ersparst du dir leichtsinnige Casts mit as, die die Prüfung abschalten, statt sie zu lenken.

TYPESCRIPT
function formatValue(
  value: string | number | Date
): string {
  if (typeof value === "string") {
    return value.trim();
  }
  if (value instanceof Date) {
    return value.toISOString();
  }
  return value.toFixed(2);
}
Jeder Zweig verengt den Typ; der letzte kann nur noch number sein.

Der in-Operator ist unbezahlbar, wenn du strukturell unterschiedliche Objekte verarbeitest, die kein Unterscheidungsfeld besitzen. Aber Vorsicht: in prüft das Vorhandensein eines Schlüssels, auch eines geerbten, nicht dessen Typ. Bei externen Daten (APIs, JSON) verengt er gemäß deinen Deklarationen, nicht gemäß dem, was zur Laufzeit tatsächlich existiert. Die klassische Falle aus der Praxis: Eine Prüfung typeof value === "object" lässt null durch, denn typeof null ist in JavaScript "object". Ergänze immer eine explizite Null-Prüfung.

TYPESCRIPT
interface Cat { meow(): void }
interface Dog { bark(): void }

function speak(animal: Cat | Dog): void {
  if ("meow" in animal) {
    animal.meow();
  } else {
    animal.bark();
  }
}
Verengung über das Vorhandensein einer Eigenschaft mit dem in-Operator.

Wenn die Guard-Logik wiederverwendbar oder für die Flussanalyse zu komplex wird, schreibst du einen eigenen Type Guard: eine Funktion, deren Rückgabetyp mit value is T annotiert ist. Der Compiler vertraut dann deiner Implementierung. Das ist ein Vertrag: Ist deine Prüfung unvollständig, führst du Laufzeitfehler wieder ein, die das Typsystem nie zu sehen bekommt. Seit TypeScript 5.5 werden einfache Prädikate in Callbacks wie filter automatisch abgeleitet, doch für exportierte Funktionen bleibt die explizite Annotation die Norm.

TYPESCRIPT
interface User { id: string; email: string }

function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    typeof value.id === "string" &&
    "email" in value &&
    typeof value.email === "string"
  );
}

const raw: unknown =
  JSON.parse('{"id":"1","email":"a@b.c"}');
if (isUser(raw)) {
  console.log(raw.email.toLowerCase());
}
Ein Prädikat, das externe Daten vor der Verwendung validiert.

Wissenscheck

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

  1. Warum genügt die Prüfung typeof value === "object" nicht, um auf ein Objekt ungleich null zu verengen?
    • Weil typeof "Object" mit großem Anfangsbuchstaben zurückgibt
    • Weil typeof null in JavaScript ebenfalls "object" ist
    • Weil typeof bei Unions nicht funktioniert
    • Weil Arrays "array" zurückgeben
  2. Welche Rückgabe-Annotation macht aus einer Funktion einen eigenen Type Guard?
    • boolean
    • value as User
    • value is User
    • asserts value
  3. Was ist das Hauptrisiko eines schlecht geschriebenen eigenen Type Guards?
    • Ein Kompilierfehler innerhalb der Funktion
    • Eine lügende Verengung: Der Compiler glaubt an einen Typ, der zur Laufzeit falsch ist
    • Ein Verlust an Kompilierleistung