TypeScript utiliza tipificación estructural (structural typing), o "tipificación por tipo pato". Para la compatibilidad de tipos, la estructura (firma) es importante, no el nombre o la procedencia del tipo.
Ejemplo:
interface Point2D { x: number; y: number; } interface Coord2D { x: number; y: number; } // Estos tipos son intercambiables: Point2D y Coord2D, porque la estructura es la misma. const foo: Point2D = { x: 1, y: 2 }; const bar: Coord2D = foo; // ¡OK!
Tipificación nominal (nominal typing): para la compatibilidad de tipos, es importante el "nombre" o "fábrica", la estructura no importa.
En TypeScript, la tipificación nominal no se soporta de forma nativa, pero se puede simular utilizando tipos enmarcados (branded types):
type USD = number & { readonly __brand: unique symbol } type EUR = number & { readonly __brand: unique symbol } let priceUSD: USD; let priceEUR: EUR; // priceUSD = priceEUR; // ¡Error! Marcas diferentes.
¿Para qué se aplica esto? Por ejemplo, para distinguir tipos que son estructuralmente idénticos pero conceptualmente diferentes: monedas, userID/tokenID, magnitudes físicas, etc.
Pregunta: ¿Por qué el siguiente código se compila sin errores, aunque Address y UserId son lógicamente tipos diferentes?
interface Address { value: string; } interface UserId { value: string; } let id: UserId = { value: "test" }; let addr: Address = id; // ¡OK!
Respuesta: Porque en TypeScript lo importante es la estructura, no el nombre del tipo. Ambos tipos son simplemente "un objeto con value: string".
Historia
Proyecto: Sistema financiero con cálculos en USD/EUR. Las sumas se transmitían a través de number. Una vez se confundieron las monedas al sumarlas; debido a la tipificación estructural, TypeScript no lo detectó. Luego se introdujeron tipos enmarcados para evitar tales errores en el momento de la compilación.
Historia
Proyecto: En el desarrollo de una API REST se utilizaron objetos para los Id de diferentes entidades (userId, groupId), ambos con un campo value: string. Por error, se sustituyó userId por groupId, y solo la lógica empresarial en el servidor detectó el error.
Historia
Proyecto: En una biblioteca de analizadores para DSL se utilizaron estructuras iguales (type value = { kind: 'num'|'str', value: number|string }). Estructuras homogéneas se mezclaron entre diferentes partes del código, lo que provocó errores lógicos. Se añadieron campos de marca artificiales para diferenciarlas.