TypeScript gebruikt structurele typering (structural typing), of "typen op basis van een eend". Voor typecompatibiliteit is de structuur (handtekening) belangrijk, niet de naam of oorsprong van het type.
Voorbeeld:
interface Point2D { x: number; y: number; } interface Coord2D { x: number; y: number; } // Deze types zijn uitwisselbaar: Point2D en Coord2D, omdat de structuur hetzelfde is. const foo: Point2D = { x: 1, y: 2 }; const bar: Coord2D = foo; // OK!
Nominale typering (nominal typing): voor typecompatibiliteit is "naam" of "fabriek" belangrijk, de structuur is onbelangrijk.
In TypeScript wordt nominale typering standaard niet ondersteund, maar kan dit worden geëmimeerd met behulp van branded types:
type USD = number & { readonly __brand: unique symbol } type EUR = number & { readonly __brand: unique symbol } let priceUSD: USD; let priceEUR: EUR; // priceUSD = priceEUR; // Fout! Verschillende merken.
Waarom zou je dit toepassen? Bijvoorbeeld, om dezelfde structuren te onderscheiden die semantisch verschillende types zijn — valuta, userID/tokenID, fysische grootheden, enz.
Vraag: Waarom compileert de volgende code zonder fouten, hoewel Address en UserId logisch verschillende types zijn?
interface Address { value: string; } interface UserId { value: string; } let id: UserId = { value: "test" }; let addr: Address = id; // OK
Antwoord: Omdat in TypeScript de structuur belangrijk is, niet de naam van het type. Beide types zijn gewoon "een object met value: string".
Verhaal
Project: Financieel systeem met berekeningen in USD/EUR. Bedragen werden doorgestuurd als number. Op een dag werden de valuta's verwisseld bij het optellen — door structurele typering ontdekte TypeScript dit niet. Later werden branded types geïntroduceerd om dergelijke fouten tijdens de compilatie uit te sluiten.
Verhaal
Project: Bij de ontwikkeling van een REST API werden objecten gebruikt voor de ID's van verschillende entiteiten (userId, groupId), beide met het veld value: string. Per ongeluk werd userId in plaats van groupId gebruikt, en alleen de bedrijfslogica op de server ontdekte de fout.
Verhaal
Project: In een bibliotheek voor parsers voor DSL werden identieke structuren gebruikt (type value = { kind: 'num'|'str', value: number|string }). Soortgelijke structuren raakten gemengd tussen verschillende delen van de code, wat leidde tot logische fouten. Kunstmatige brand-velden werden toegevoegd voor scheiding.