ProgrammatieTypeScript architect

Wat is het verschil tussen structurele en nominale typering in TypeScript? Is het mogelijk om nominale typering te implementeren, en zo ja, hoe? Welke problemen kan dit oplossen?

Slaag voor sollicitatiegesprekken met de Hintsage AI-assistent

Antwoord

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.


Misleidende vraag

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".


Voorbeelden van echte fouten door onbekendheid met de nuances van het onderwerp


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.