Type aliases e interfaces en TypeScript | Nicolás Garzóntype
interface
TypeScript
type Product = {
id: string;
name: string;
};
TypeScript
interface Product {
id: string;
name: string;
}
Un alias puede nombrar cualquier tipo:
TypeScript
type Identifier = string | number;
type Coordinates = readonly [x: number, y: number];
type ProductMap = Record<string, Product>;
type Loader = (id: string) => Promise<Product>;
También puede describir objetos.
Una interface declara principalmente una forma de objeto, función o constructor.
TypeScript
interface ProductRepository {
findById(id: string): Promise<Product | undefined>;
save(product: Product): Promise<void>;
}
TypeScript
interface Cart {
add(item: CartItem): void;
remove(itemId: string): void;
calculateTotal(): number;
}
Definir métodos en interfaces es completamente normal. Una interface expresa un contrato; no obliga a utilizar una clase.
TypeScript
const cart: Cart = createCart();
TypeScript
interface Formatter {
(value: number): string;
}
Es válido, aunque un type alias suele ser más directo para una firma simple:
TypeScript
type Formatter = (value: number) => string;
TypeScript
interface ProductConstructor {
new (id: string, name: string): Product;
}
TypeScript
interface Entity {
id: string;
}
interface Product extends Entity {
name: string;
price: number;
}
Una interface puede extender varias interfaces:
TypeScript
interface AuditedProduct
extends Product, Timestamped {}
La afirmación “los types no pueden expandirse” es incorrecta.
TypeScript
type Entity = {
id: string;
};
type Product = Entity & {
name: string;
price: number;
};
También pueden derivarse mediante generics, mapped types y conditional types.
TypeScript
type Named = {
name: string;
};
interface User extends Named {
email: string;
}
Funciona cuando el alias representa una forma de objeto conocida estáticamente. Una interface no puede extender directamente una union.
TypeScript
type Result = Success | Failure;
interface ExtendedResult extends Result {}
Esto no es válido porque una union no tiene una forma única fija.
TypeScript
interface Product {
id: string;
}
type ProductPreview = Product & {
selected: boolean;
};
Ambos sistemas se combinan.
Dos interfaces con el mismo nombre en el mismo scope pueden fusionarse.
TypeScript
interface Window {
applicationVersion: string;
}
interface Window {
currentUser?: User;
}
El resultado contiene ambos miembros.
Los type aliases no se redeclaran:
TypeScript
type Product = { id: string };
type Product = { name: string };
Produce un error de identificador duplicado.
Declaration merging aporta para:
- Extender APIs globales.
- Tipar plugins.
- Module augmentation.
- Librerías diseñadas como contratos abiertos.
En modelos de dominio internos puede ocultar dónde se añadió una propiedad. Usa nombres únicos y módulos claros.
TypeScript
interface Settings {
theme: string;
}
interface Settings {
theme: number;
}
Las propiedades no funcionales deben tener tipos compatibles.
Los métodos pueden formar overloads durante el merge.
TypeScript
interface Product {
readonly id: string;
description?: string;
}
Tienen el mismo significado estático que en object type aliases.
TypeScript
interface Scores {
[playerId: string]: number;
}
TypeScript
interface Repository<TEntity> {
findById(id: string): Promise<TEntity | undefined>;
save(entity: TEntity): Promise<void>;
}
TypeScript
class InMemoryProductRepository
implements ProductRepository {
async findById(id: string) {
return products.get(id);
}
async save(product: Product) {
products.set(product.id, product);
}
}
implements comprueba el lado de instancia de la clase. No copia código ni cambia el prototipo.
Interfaces no son “más apropiadas para HTTP” por definición.
TypeScript
type ProductResponse = {
id: string;
price_in_cents: number;
};
Podría ser type o interface. Lo importante es:
- Representar el contrato externo.
- Validarlo en runtime.
- Transformarlo al modelo interno.
- Unions.
- Tuples.
- Primitivos nombrados.
- Function types simples.
- Mapped y conditional types.
- Composición cerrada.
- Formas de objetos públicas.
- Contratos que una clase implementará.
- APIs extensibles por declaration merging.
- Librerías donde consumidores ampliarán el contrato.
Estas son tendencias, no prohibiciones.
Si ambos sirven, sigue la convención del proyecto. Cambiar entre type e interface sin una razón no mejora el sistema.
TypeScript
interface PaymentGateway {
charge(input: ChargeInput): Promise<ChargeResult>;
}
type ChargeResult =
| { status: "approved"; paymentId: string }
| { status: "rejected"; reason: string };
La interface expresa una capacidad implementable. El type expresa variantes cerradas de resultado.
- Decir que type aliases no pueden extenderse o componerse.
- Afirmar que interfaces son solo para HTTP.
- Evitar métodos en interfaces.
- Crear una clase únicamente porque existe una interface.
- Usar declaration merging accidentalmente.
- Intentar extender una union mediante interface.
- Pensar que implements copia implementación.
- Elegir una sintaxis diferente para cada objeto sin convención.
- Tipar una respuesta externa y omitir validación runtime.
- Type puede nombrar cualquier tipo.
- Interface describe principalmente formas de objetos, llamadas y constructores.
- Ambos modelan objetos estructuralmente.
- Type compone mediante intersections y transformaciones.
- Interface extiende y puede fusionarse.
- Declaration merging vuelve una interface abierta.
- Implements solo comprueba compatibilidad estática.
- La elección depende del contrato y de si debe ser abierto o cerrado.
¿Por qué type Status = "idle" | "loading" no puede reemplazarse directamente por una interface?
Respuesta
Porque una interface describe una forma de objeto, llamada o constructor; no puede representar directamente una union de valores primitivos literales.
Extensión, intersections y declaration merging compara las distintas formas de ampliar contratos y sus conflictos.