Extensión, intersections y declaration merging | Nicolás Garzón
TypeScript ofrece varias formas de ampliar contratos, pero cada una expresa una intención distinta.
Texto
Copiar interface extends → herencia estructural abierta
intersection & → combinación de restricciones
merging → declaraciones con el mismo nombre se unen
augmentation → ampliar un módulo o global existente
TypeScript
Copiar interface Entity {
id: string ;
}
interface Product extends Entity {
name: string ;
} La interface hija debe mantener miembros compatibles.
TypeScript
Copiar interface Named {
value: string ;
}
interface Numeric {
value: number ;
}
interface Invalid extends Named , Numeric { } TypeScript detecta inmediatamente que value no puede heredarse con tipos incompatibles.
TypeScript
Copiar type Combined = Named & Numeric; Puede crearse, pero Combined["value"] resulta never. El conflicto aparece al usar el tipo.
Usa extends cuando modelas una relación de contrato clara entre object types.
Compones aliases.
Construyes un tipo derivado temporal.
Añades una capacidad genérica.
Combinas resultados de transformaciones.
TypeScript
Copiar interface ApplicationConfig {
apiUrl: string ;
}
interface ApplicationConfig {
timeout: number ;
} El resultado incluye ambos miembros.
TypeScript
Copiar type ApplicationConfig = {
apiUrl: string ;
} ; No puede redeclararse con el mismo nombre. Esto ayuda cuando quieres que el contrato tenga un único origen visible.
TypeScript
Copiar interface Parser {
parse ( value: string ) : Product;
}
interface Parser {
parse ( value: ArrayBuffer) : Product;
} Los métodos forman overloads.
Las declaraciones posteriores pueden tener prioridad en grupos de overloads, con reglas especiales para firmas literales. No diseñes una API cuyo comportamiento dependa de un orden de archivos difícil de seguir.
Una namespace puede fusionarse con una clase, función o enum para añadir miembros estáticos o tipos relacionados.
TypeScript
Copiar function buildProduct ( ) { }
namespace buildProduct {
export type Options = {
validate: boolean ;
} ;
} Este patrón existe en APIs históricas, pero para código moderno suelen ser más claros módulos y exports separados.
TypeScript
Copiar import "some-library" ;
declare module "some-library" {
interface Client {
traceId? : string ;
}
} La declaración amplía el tipo del módulo existente. Debe coincidir con una modificación runtime real o con una capacidad ya presente que las declaraciones omitían.
TypeScript
Copiar declare module "some-library" {
interface Client {
reset ( ) : void ;
}
} Si el objeto real no tiene reset, el código compilará pero fallará al ejecutar.
TypeScript
Copiar export { } ;
declare global {
interface Window {
applicationVersion: string ;
}
} El export {} convierte el archivo en módulo y declare global abre explícitamente el global.
Interfaces globales como Window, HTMLElementTagNameMap o APIs de plugins se benefician de ser ampliables.
Los modelos del dominio suelen beneficiarse de contratos cerrados y transformaciones explícitas.
TypeScript
Copiar type WithId< T > = T & {
id: string ;
} ; TypeScript
Copiar function addId < T extends object> ( value: T ) : WithId< T > {
return {
... value,
id: crypto. randomUUID ( ) ,
} ;
} TypeScript no tiene un operador de “sobrescribir” propiedades mediante &.
TypeScript
Copiar type Replace< T , TReplacement> =
Omit< T , keyof TReplacement> & TReplacement; TypeScript
Copiar type EditableProduct = Replace<
Product,
{ id: number }
> ; Esta transformación elimina primero los miembros reemplazados.
Intersections pueden mostrarse de forma poco legible en el editor.
TypeScript
Copiar type Simplify< T > = {
[ K in keyof T ] : T [ K ] ;
} & { } ; Ayuda a mostrar una forma final, pero no cambia su significado estructural.
TypeScript
Copiar interface ApplicationPlugins { }
interface ApplicationPlugins {
analytics: AnalyticsPlugin;
}
interface ApplicationPlugins {
notifications: NotificationPlugin;
} Este patrón permite que módulos independientes registren capacidades en un contrato abierto.
Usar intersection esperando sobrescritura de propiedades.
Elegir merging para modelos que deberían ser cerrados.
Ampliar un módulo sin implementar la capacidad runtime.
Crear globals desde cualquier archivo sin declare global.
Depender del orden complejo de overloads fusionados.
Usar namespace merging donde módulos normales son más claros.
Combinar muchas intersections sin revisar conflictos.
Extender contratos externos para evitar adaptar los datos.
extends expresa herencia estructural de interfaces.
& combina restricciones y no sobrescribe.
Declaration merging vuelve contratos abiertos.
Augmentation modifica la visión estática de módulos o globals.
Toda ampliación debe coincidir con el runtime.
Para reemplazar propiedades, elimina y añade explícitamente.
Elige contratos abiertos solo donde la extensibilidad sea intencional.
¿Por qué A & { id: number } no reemplaza un id: string existente en A?
Respuesta Porque la intersection exige cumplir ambos tipos simultáneamente; la propiedad terminaría como string & number, es decir, never.
keyof, typeof e indexed access types permite derivar tipos desde contratos existentes.