ProgramaciónArquitecto de TypeScript

¿Cuál es la diferencia entre la tipificación estructural y la tipificación nominal en TypeScript? ¿Se puede implementar la tipificación nominal y, de ser así, cómo? ¿Qué problemas puede resolver esto?

Supere entrevistas con el asistente de IA Hintsage

Respuesta

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 trampa

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


Ejemplos de errores reales debido a la falta de conocimiento de los matices del tema


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.