El sistema de tipos de TypeScript se basa fundamentalmente en el tipado estructural (Structural Typing / Duck Typing): la compatibilidad de tipos se determina exclusivamente por su forma (el conjunto de propiedades y métodos), y no por una declaración nominal explícita (como en Java o C#). Si dos tipos diferentes tienen exactamente la misma estructura de campos, el compilador los considera mutuamente intercambiables.
Para describir las formas de los datos estructurales en TypeScript, existen dos herramientas principales:
interface): contratos abiertos para describir objetos y clases. Permiten la herencia mediante extends y el mecanismo de fusión automática de declaraciones (Declaration Merging).type): alias sintácticos flexibles para cualquier entidad del sistema de tipos (objetos, primitivos, uniones union, intersecciones intersection, tuplas tuples y firmas de funciones).En el desarrollo moderno, la elección entre interface y type está dictada por el contexto arquitectónico: las interfaces son ideales para contratos públicos extensibles de librerías y jerarquías de programación orientada a objetos (OOP), mientras que los alias de tipos son indispensables al crear tipos composicionales y calculados complejos.
La diferencia entre interface y type se reduce a la apertura (abiertos a la fusión) frente a la fijación (definición inmutable):
+-------------------------------------------------------------------------+
| INTERFACE vs TYPE: MODELO MENTAL |
+-------------------------------------------------------------------------+
| INTERFACE (Contrato abierto): |
| interface User { id: string } |
| interface User { name: string } ---> Resultado: { id: string; name: string }|
| (Declaration Merging funciona automáticamente) |
+-------------------------------------------------------------------------+
| TYPE ALIAS (Alias cerrado): |
| type User = { id: string } |
| type User = { name: string } ---> ERROR TS2300: Duplicate identifier|
| (La redeclaración está estrictamente prohibida) |
+-------------------------------------------------------------------------+
| CAPACIDADES DE COMPOSICIÓN: |
| type Role = 'admin' | 'user' ---> ¡La interfaz NO puede hacer uniones!|
| type Pair = [number, string] ---> ¡La interfaz NO puede hacer tuplas!|
+-------------------------------------------------------------------------+&)
El compilador compara la lista de propiedades requeridas del tipo objetivo con el valor real.
Para cada propiedad, se verifica recursivamente la compatibilidad de los tipos de sus valores.
Para literales de objetos directos, el compilador bloquea campos desconocidos para prevenir erratas.
El resultado de la verificación de la relación de tipos se almacena en la caché interna del compilador para acelerar la compilación.
¿Qué sucede si se declaran dos interfaces con el mismo nombre en diferentes archivos del proyecto: `interface Window { analytics: Tracker }`?
process.env en Node.js o del objeto global window mediante la fusión de interfaces.req.user a la interfaz base de la solicitud.IUserRepository) para implementar el patrón de inversión de dependencias (Dependency Inversion).type TenantEntity = BaseEntity & Auditable & TenantBound).extends vs &#Las interfaces se extienden utilizando la palabra clave extends, mientras que los tipos se combinan mediante el operador de intersección &:
// 1. Interfaces: herencia mediante extends
interface Identified {
id: string;
}
interface User extends Identified {
email: string;
role: 'admin' | 'user';
}
// 2. Type Aliases: composición mediante intersección (&)
type Timestamped = {
createdAt: Date;
updatedAt: Date;
};
type FullUser = User & Timestamped;
const activeUser: FullUser = {
id: 'usr_9482',
email: '[email protected]',
role: 'admin',
createdAt: new Date(),
updatedAt: new Date(),
};La interfaz verifica estrictamente la compatibilidad de las propiedades sobrescritas. Si hay incompatibilidad, el compilador emite un error de inmediato. En el caso de type &, el compilador crea un tipo never para la propiedad en conflicto:
interface BaseNode {
id: string;
}
// ERROR: interface prohíbe explícitamente la sobrescritura incompatible
// interface NumberNode extends BaseNode {
// id: number; // TS2430: Interface 'NumberNode' incorrectly extends interface 'BaseNode'.
// }
// En Type Alias, la intersección de primitivos incompatibles crea 'never':
type Alpha = { id: string };
type Beta = { id: number };
type Combined = Alpha & Beta;
// ¡El campo id tiene el tipo: string & number === never!
// const bad: Combined = { id: 123 }; // ERROR: Type 'number' is not assignable to type 'never'.type puede hacer pero interface no#Los alias de tipos pueden representar primitivos, tuplas, uniones y propiedades calculadas (Mapped Types):
// Uniones (Unions)
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
// Tuplas (Tuples)
type Coordinate = [latitude: number, longitude: number];
// Alias de primitivos
type UUID = string;
// Extracción de tipos de propiedades (Indexed Access)
type UserRole = User['role']; // 'admin' | 'user'
// Tipos mapeados (Mapped Types) - ¡prohibidos dentro de una interfaz!
type ReadonlyRecord<T> = {
readonly [K in keyof T]: T[K];
};¿Por qué la expresión `const user: User = { id: '1', email: '[email protected]', extra: true }` genera un error, pero al pasarla a través de una variable intermedia `const temp = { id: '1', email: '[email protected]', extra: true }; const user: User = temp;` el compilador no dice nada?
Si dos equipos diferentes declaran una interfaz UserConfig en el ámbito global, TypeScript fusionará sus campos. Esto puede enmascarar errores ocultos y colisiones.
Решение: Utiliza type para estructuras internas o encapsula las interfaces en módulos ES con nombre mediante export.
Al fusionar ciegamente tipos mediante &, es fácil crear propiedades de tipo never:
type ResA = { status: 200; data: string };
type ResB = { status: 404; error: string };
type Res = ResA & ResB; // ¡El campo status se convirtió en (200 & 404) === never!Para estados mutuamente excluyentes, utiliza siempre la unión (|) en lugar de la intersección (&).