TypeScript wykorzystuje typowanie strukturalne (structural typing), znane również jako "typowanie na podstawie kaczki". Dla zgodności typów ważna jest struktura (sygnatura), a nie nazwa czy pochodzenie typu.
Przykład:
interface Point2D { x: number; y: number; } interface Coord2D { x: number; y: number; } // Te typy są zamienne: Point2D i Coord2D, ponieważ struktura jest taka sama. const foo: Point2D = { x: 1, y: 2 }; const bar: Coord2D = foo; // OK!
Typowanie nominalne (nominal typing): dla zgodności typów ważna jest "nazwa" lub "fabryka", struktura nie jest ważna.
W TypeScript typowanie nominalne nie jest natywnie wspierane, ale można je emulować przy użyciu typów oznaczonych (branded types):
type USD = number & { readonly __brand: unique symbol } type EUR = number & { readonly __brand: unique symbol } let priceUSD: USD; let priceEUR: EUR; // priceUSD = priceEUR; // Błąd! Różne marki.
Po co to stosować? Na przykład, aby rozróżniać typy, które mają tę samą strukturę, ale mają różne znaczenia — waluty, userID/tokenID, wielkości fizyczne itp.
Pytanie: Dlaczego poniższy kod kompiluje się bez błędów, chociaż Address i UserId to logicznie różne typy?
interface Address { value: string; } interface UserId { value: string; } let id: UserId = { value: "test" }; let addr: Address = id; // OK
Odpowiedź: Ponieważ w TypeScript ważna jest struktura, a nie nazwa typu. Oba typy to po prostu "obiekt z value: string".
Historia
Projekt: System finansowy z transakcjami w USD/EUR. Kwoty przekazywano jako number. Pewnego razu pomylono waluty podczas dodawania — przez typowanie strukturalne TypeScript tego nie wykrył. Później wprowadzono typy oznaczone, aby wyeliminować takie błędy na etapie kompilacji.
Historia
Projekt: W rozwijanym REST API używano obiektów dla identyfikatorów różnych bytów (userId, groupId), oba z polem value: string. Z powodu błędu userId był podstawiany zamiast groupId, a błąd wykrywała jedynie logika biznesowa na serwerze.
Historia
Projekt: W bibliotece parserów dla DSL używano tych samych struktur (type value = { kind: 'num'|'str', value: number|string }). Jednorodne struktury pomieszały się między różnymi częściami kodu, co prowadziło do błędów logicznych. Dodano sztuczne pola brand do rozdzielenia.