TypeScript ofrece dos construcciones principales para modelar tipos propios: type e interface. Aunque ambas pueden describir la forma de un objeto, sus capacidades y su comportamiento en tiempo de compilación no son idénticos.
type: alias de tipos
Un alias creado con type asigna un nuevo nombre a cualquier tipo válido: primitivos, objetos, uniones, intersecciones, tuplas, funciones, tipos condicionales y tipos mapeados.
type IdUsuario = string | number;
type Activo = boolean;
type Coordenada = [number, number];
type Usuario = {
id: IdUsuario;
nombre: string;
edad: number;
activo: Activo;
};
type RespuestaApi<T> =
| { ok: true; datos: T }
| { ok: false; error: string };
interface: contratos de forma
interface define la estructura que debe cumplir un objeto o una clase. Su sintaxis está orientada a describir la forma de entidades y a establecer contratos en programación orientada a objetos.
interface Usuario {
id: string | number;
nombre: string;
edad: number;
activo: boolean;
}
interface Servicio {
ejecutar(): void;
}
Similitudes
Describen objetos y funciones
Ambas pueden modelar objetos y firmas de función, aunque la sintaxis difiere.
type PuntoType = {
x: number;
y: number;
};
interface PuntoInterface {
x: number;
y: number;
}
type ComparadorType = (a: number, b: number) => number;
interface ComparadorInterface {
(a: number, b: number): number;
}
Compatibilidad entre ambas
Las interfaces y los alias no son mutuamente excluyentes. Una interfaz puede extender un alias y viceversa.
type Base = {
nombre: string;
};
interface Empleado extends Base {
departamento: string;
}
interface Persona {
nombre: string;
}
type Cliente = Persona & {
credito: number;
};
Implementación en clases
Una clase puede implementar tanto una interfaz como un alias de tipo objeto. La excepción son los alias de unión, que no pueden implementarse.
interface Volador {
volar(): void;
}
type Nadador = {
nadar(): void;
};
class Pato implements Volador, Nadador {
volar() {}
nadar() {}
}
type Serializable =
| { serializar(): string }
| { toJSON(): string };
// Error: una clase no puede implementar una unión de tipos.
// class Cache implements Serializable {}
Diferencias principales
1. Alias de tipos primitivos
type puede renombrar tipos primitivos; interface no admite esta operación.
type Nombre = string;
type Identificador = number;
type UUID = `${string}-${string}-${string}-${string}-${string}`;
2. Uniones, intersecciones y tuplas
Los alias permiten definir uniones, intersecciones y tuplas. Las interfaces no expresan directamente uniones ni tuplas.
type EstadoPedido = 'pendiente' | 'procesando' | 'enviado';
type Coordenada = [latitud: number, longitud: number];
type UsuarioConPermisos = Usuario & {
permisos: string[];
};
type Resultado = { exito: true; valor: number } | { exito: false; mensaje: string };
3. Fusión de declaraciones
Si se declara varias veces una interfaz con el mismo identificador en el mismo ámbito, TypeScript las fusiona en una sola. Los alias no permiten esta fusión.
interface Configuracion {
host: string;
}
interface Configuracion {
puerto: number;
}
const c: Configuracion = {
host: 'localhost',
puerto: 3000,
};
// Error: no se puede declarar dos veces el mismo alias.
// type Configuracion = { host: string };
// type Configuracion = { puerto: number };
4. Comportamiento con firmas de índice
Un objeto definido mediante interface no se puede asignar directamente a una firma de índice, mientras que uno definido con type sí. Esto se debe a que las interfaces son abiertas a la fusión de declaraciones y, por tanto, sus propiedades no se consideran completamente conocidas.
interface Diccionario {
[clave: string]: string;
}
type TituloType = {
titulo: string;
};
interface TituloInterface {
titulo: string;
}
const a: TituloType = { titulo: 'Inicio' };
const b: TituloInterface = { titulo: 'Inicio' };
const dict: Diccionario = a; // OK
// const dict2: Diccionario = b; // Error: falta la firma de índice.
Cuándo usar cada uno
- Prefiere
interfacecuando describas la forma de objetos, APIs públicas, contratos de clases o cuando esperes ampliar la definición mediante fusión de declaraciones. - Prefiere
typepara alias de primitivos, uniones, intersecciones, tuplas, tipos mapeados, tipos condicionales y cualquier tipo que resulte de una composición más compleja.
Ejemplo práctico
interface Producto {
nombre: string;
precio: number;
sku: string;
}
type Descuento =
| { tipo: 'porcentaje'; valor: number }
| { tipo: 'fijo'; valor: number };
function calcularTotal(producto: Producto, descuento: Descuento): number {
if (descuento.tipo === 'porcentaje') {
return producto.precio * (1 - descuento.valor / 100);
}
return Math.max(0, producto.precio - descuento.valor);
}
const portatil: Producto = {
nombre: 'Portátil',
precio: 1500,
sku: 'P-001',
};
const rebaja: Descuento = { tipo: 'porcentaje', valor: 10 };
console.log(calcularTotal(portatil, rebaja)); // 1350
En la mayoría de los casos, ambas construcciones pueden usarse indistintamente para objetos simples. Sin embargo, elegir la adecuada mejora la claridad del código y evita limitaciones en escenarios avanzados.