ProgrammierungTypeScript-Architekt

Was ist der Unterschied zwischen struktureller und nomineller Typisierung in TypeScript? Kann nominelle Typisierung realisiert werden, und wenn ja, wie? Welche Probleme kann das lösen?

Bestehen Sie Vorstellungsgespräche mit dem Hintsage-KI-Assistenten

Antwort

TypeScript verwendet strukturelle Typisierung (structural typing) oder "Enten-Typisierung". Für die Typkompatibilität ist die Struktur (Signatur) wichtig, nicht der Name oder Ursprung des Typs.

Beispiel:

interface Point2D { x: number; y: number; } interface Coord2D { x: number; y: number; } // Diese Typen sind austauschbar: Point2D und Coord2D, weil die Struktur gleich ist. const foo: Point2D = { x: 1, y: 2 }; const bar: Coord2D = foo; // OK!

Nominelle Typisierung (nominal typing): Für die Typkompatibilität ist der "Name" oder "Factory" wichtig, die Struktur ist unwichtig.

In TypeScript wird nominelle Typisierung nicht standardmäßig unterstützt, aber sie kann mit Hilfe von branded types emuliert werden:

type USD = number & { readonly __brand: unique symbol } type EUR = number & { readonly __brand: unique symbol } let priceUSD: USD; let priceEUR: EUR; // priceUSD = priceEUR; // Fehler! Verschiedene Marken.

Warum sollte man das anwenden? Zum Beispiel, um strukturell identische, aber konzeptionell unterschiedliche Typen zu unterscheiden — Währungen, userID/tokenID, physikalische Größen usw.


Fangfrage

Frage: Warum wird der folgende Code ohne Fehler kompiliert, obwohl Address und UserId logisch verschiedene Typen sind?

interface Address { value: string; } interface UserId { value: string; } let id: UserId = { value: "test" }; let addr: Address = id; // OK

Antwort: Weil in TypeScript die Struktur wichtig ist, nicht der Typname. Beide Typen sind einfach "Objekt mit value: string".


Beispiele für reale Fehler aufgrund mangelnden Wissens über die Feinheiten des Themas


Geschichte

Projekt: Finanzsystem mit Berechnungen in USD/EUR. Beträge wurden als number übergeben. Einmal wurden die Währungen beim Addieren verwechselt — aufgrund der strukturellen Typisierung hat TypeScript dies nicht erkannt. Später wurden branded types eingeführt, um solche Fehler zur Kompilierzeit zu vermeiden.


Geschichte

Projekt: Bei der Entwicklung eines REST APIs wurden Objekte für die IDs verschiedener Entitäten (userId, groupId) verwendet, beide mit dem Feld value: string. Aus Versehen wurde userId anstelle von groupId eingesetzt, und nur die Geschäftslogik auf dem Server erkannte den Fehler.


Geschichte

Projekt: In einer Bibliothek für DSL-Parser wurden identische Strukturen verwendet (type value = { kind: 'num'|'str', value: number|string }). Homogene Strukturen vermischten sich zwischen verschiedenen Teilen des Codes, was zu logischen Fehlern führte. Künstliche Brand-Felder wurden hinzugefügt, um sie zu trennen.