
Tipos de unión con el operador |
Los tipos de unión permiten expresar que un valor puede pertenecer a uno de varios tipos posibles. El operador | combina dos o más tipos, creando un tipo que acepta cualquier valor válido de sus miembros:
let identificador: string | number
identificador = "USR-001"
identificador = 42
// identificador = true // Error: Type 'boolean' is not assignable to type 'string | number'
Cuando una variable tiene un tipo de unión, TypeScript solo permite acceder a las propiedades y métodos que son comunes a todos los miembros de la unión:
function mostrarLongitud(valor: string | string[]): number {
// .length existe tanto en string como en string[]
return valor.length
}
console.log(mostrarLongitud("hola")) // 4
console.log(mostrarLongitud(["a", "b", "c"])) // 3
Si intentamos acceder a un método que solo existe en uno de los tipos, TypeScript emite un error hasta que realicemos una comprobación:
function procesar(valor: string | number): string {
// valor.toUpperCase() // Error: Property 'toUpperCase' does not exist on type 'number'
if (typeof valor === "string") {
return valor.toUpperCase()
}
return valor.toFixed(2)
}
console.log(procesar("typescript")) // "TYPESCRIPT"
console.log(procesar(3.14159)) // "3.14"
Uniones con null y undefined
Un uso frecuente de las uniones es representar valores que pueden ser nulos o indefinidos:
function buscarUsuario(id: number): string | null {
if (id === 1) return "Ana"
if (id === 2) return "Carlos"
return null
}
const nombre = buscarUsuario(3)
if (nombre !== null) {
console.log(nombre.toUpperCase()) // TypeScript sabe que es string
}
El operador de coalescencia nula ?? simplifica el manejo de valores null o undefined:
function obtenerConfig(clave: string): string | undefined {
const configs: Record<string, string> = { tema: "oscuro", idioma: "es" }
return configs[clave]
}
const tema = obtenerConfig("tema") ?? "claro"
const fuente = obtenerConfig("fuente") ?? "sans-serif"
console.log(tema) // "oscuro"
console.log(fuente) // "sans-serif"
Uniones en parámetros y retornos de funciones
Las uniones permiten crear funciones flexibles que manejan varios tipos de entrada:
function formatearFecha(fecha: Date | string | number): string {
if (typeof fecha === "string") {
return new Date(fecha).toLocaleDateString("es-ES")
}
if (typeof fecha === "number") {
return new Date(fecha).toLocaleDateString("es-ES")
}
return fecha.toLocaleDateString("es-ES")
}
console.log(formatearFecha(new Date()))
console.log(formatearFecha("2025-06-15"))
console.log(formatearFecha(1718409600000))
También resultan útiles para representar distintos formatos de entrada en APIs:
type Criterio = string | { campo: string; valor: string } | string[]
function buscar(criterio: Criterio): string {
if (typeof criterio === "string") {
return `Busqueda por texto: ${criterio}`
}
if (Array.isArray(criterio)) {
return `Busqueda por etiquetas: ${criterio.join(", ")}`
}
return `Busqueda por campo ${criterio.campo} = ${criterio.valor}`
}
console.log(buscar("typescript"))
console.log(buscar(["frontend", "web"]))
console.log(buscar({ campo: "autor", valor: "Ana" }))
Tipos de intersección con el operador &
Los tipos de intersección combinan múltiples tipos en uno solo que posee todas las propiedades de cada tipo constituyente. El operador & crea un tipo que es la unión de las propiedades de ambos lados:
type ConNombre = {
nombre: string
}
type ConEdad = {
edad: number
}
type Persona = ConNombre & ConEdad
const persona: Persona = {
nombre: "Elena",
edad: 30
}
console.log(`${persona.nombre}, ${persona.edad} anios`)
En una unión (
A | B) el valor es A o B. En una intersección (A & B) el valor es A y B simultáneamente. La unión amplía las posibilidades; la intersección amplía las propiedades.
Composición de tipos con intersecciones
Las intersecciones permiten componer tipos complejos a partir de piezas más pequeñas y reutilizables:
type Timestamps = {
creadoEn: Date
actualizadoEn: Date
}
type SoftDelete = {
eliminado: boolean
eliminadoEn: Date | null
}
type DatosProducto = {
id: string
nombre: string
precio: number
}
type Producto = DatosProducto & Timestamps & SoftDelete
const producto: Producto = {
id: "PROD-001",
nombre: "Laptop",
precio: 1299,
creadoEn: new Date("2025-01-01"),
actualizadoEn: new Date("2025-03-15"),
eliminado: false,
eliminadoEn: null
}
Esta estrategia es especialmente útil para añadir metadatos transversales a múltiples entidades:
type Auditable = {
creadoPor: string
modificadoPor: string
}
type Versionable = {
version: number
historial: string[]
}
type DatosDocumento = {
titulo: string
contenido: string
}
type Documento = DatosDocumento & Auditable & Versionable
function crearDocumento(titulo: string, contenido: string, autor: string): Documento {
return {
titulo,
contenido,
creadoPor: autor,
modificadoPor: autor,
version: 1,
historial: [`v1: Creado por ${autor}`]
}
}
const doc = crearDocumento("Manual", "Contenido del manual", "Ana")
console.log(doc.historial) // ["v1: Creado por Ana"]
Intersecciones con interfaces
Las intersecciones también funcionan con interfaces, permitiendo combinar contratos existentes:
interface Serializable {
toJSON(): string
}
interface Clonable {
clonar(): this
}
interface Imprimible {
imprimir(): void
}
type ObjetoCompleto = Serializable & Clonable & Imprimible
class Nota implements ObjetoCompleto {
constructor(private texto: string) {}
toJSON(): string {
return JSON.stringify({ texto: this.texto })
}
clonar(): this {
return new Nota(this.texto) as this
}
imprimir(): void {
console.log(`Nota: ${this.texto}`)
}
}
Conflictos en intersecciones
Cuando dos tipos tienen una propiedad con el mismo nombre pero tipos incompatibles, la intersección produce el tipo never para esa propiedad, lo que impide crear valores válidos:
type A = { valor: string }
type B = { valor: number }
type Conflicto = A & B
// const imposible: Conflicto = { valor: ??? }
// No hay ningun valor que sea string y number a la vez
// El tipo de 'valor' es 'never'
Si los tipos son compatibles, la intersección los combina correctamente:
type Base = { id: string; nombre: string }
type Extension = { nombre: string; email: string }
type Combinado = Base & Extension
const combinado: Combinado = {
id: "1",
nombre: "Ana", // nombre es string en ambos, no hay conflicto
email: "ana@ejemplo.com"
}
Combinación con type aliases
Los type aliases proporcionan nombres reutilizables para uniones e intersecciones complejas. Esto mejora la legibilidad y facilita el mantenimiento:
type Id = string | number
type Estado = "activo" | "inactivo" | "pendiente"
type Respuesta<T> = { datos: T; exito: true } | { error: string; exito: false }
function obtenerUsuario(id: Id): Respuesta<{ nombre: string; estado: Estado }> {
if (typeof id === "number" && id > 0) {
return {
datos: { nombre: "Usuario " + id, estado: "activo" },
exito: true
}
}
return { error: "ID no válido", exito: false }
}
const resultado = obtenerUsuario(5)
if (resultado.exito) {
console.log(resultado.datos.nombre)
} else {
console.log(resultado.error)
}
Los type aliases pueden combinar uniones e intersecciones para modelar dominios complejos:
type MetodoPago = "tarjeta" | "transferencia" | "efectivo"
type DireccionEnvio = {
calle: string
ciudad: string
codigoPostal: string
}
type InfoFacturacion = {
nif: string
razonSocial: string
}
type OpcionesEnvio = {
direccion: DireccionEnvio
urgente: boolean
}
type PedidoOnline = {
items: { nombre: string; cantidad: number; precio: number }[]
metodoPago: MetodoPago
} & OpcionesEnvio & InfoFacturacion
type PedidoTienda = {
items: { nombre: string; cantidad: number; precio: number }[]
metodoPago: MetodoPago
tienda: string
}
type Pedido = PedidoOnline | PedidoTienda
function calcularTotal(pedido: Pedido): number {
return pedido.items.reduce((total, item) => total + item.cantidad * item.precio, 0)
}
function esOnline(pedido: Pedido): pedido is PedidoOnline {
return "direccion" in pedido
}
function procesarPedido(pedido: Pedido): string {
const total = calcularTotal(pedido)
if (esOnline(pedido)) {
const envio = pedido.urgente ? " (URGENTE)" : ""
return `Online${envio} a ${pedido.direccion.ciudad}: ${total}e`
}
return `Tienda ${pedido.tienda}: ${total}e`
}
Uniones de tipos objeto
Las uniones de tipos objeto son habituales cuando distintos estados comparten parte de su estructura:
type EventoSistema =
| { tipo: "inicio"; timestamp: Date }
| { tipo: "error"; timestamp: Date; mensaje: string; codigo: number }
| { tipo: "fin"; timestamp: Date; duracion: number }
function registrarEvento(evento: EventoSistema): string {
const fecha = evento.timestamp.toISOString()
switch (evento.tipo) {
case "inicio":
return `[${fecha}] Sistema iniciado`
case "error":
return `[${fecha}] Error ${evento.codigo}: ${evento.mensaje}`
case "fin":
return `[${fecha}] Finalizado en ${evento.duracion}ms`
}
}
const eventos: EventoSistema[] = [
{ tipo: "inicio", timestamp: new Date() },
{ tipo: "error", timestamp: new Date(), mensaje: "Timeout", codigo: 408 },
{ tipo: "fin", timestamp: new Date(), duracion: 1500 }
]
for (const evento of eventos) {
console.log(registrarEvento(evento))
}
Un patrón útil es extraer partes comunes con intersecciones y luego unir las variantes:
type MetadatosBase = {
id: string
versión: number
creadoEn: Date
}
type ConfiguracionServidor = MetadatosBase & {
tipo: "servidor"
host: string
puerto: number
ssl: boolean
}
type ConfiguracionBBDD = MetadatosBase & {
tipo: "bbdd"
conexion: string
maxConexiones: number
}
type ConfiguracionCache = MetadatosBase & {
tipo: "cache"
ttl: number
maxEntradas: number
}
type Configuracion = ConfiguracionServidor | ConfiguracionBBDD | ConfiguracionCache
function describir(config: Configuracion): string {
const base = `[${config.id} v${config.version}]`
switch (config.tipo) {
case "servidor":
return `${base} Servidor: ${config.host}:${config.puerto} SSL=${config.ssl}`
case "bbdd":
return `${base} BBDD: ${config.conexion} (max: ${config.maxConexiones})`
case "cache":
return `${base} Cache: TTL=${config.ttl}s, max=${config.maxEntradas}`
}
}
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en TypeScript
Documentación oficial de TypeScript
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, TypeScript es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de TypeScript
Explora más contenido relacionado con TypeScript y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Comprender la sintaxis y el comportamiento de los tipos de union con el operador |. Dominar los tipos de intersección con el operador & para combinar estructuras. Aplicar narrowing básico para trabajar con uniones de forma segura. Combinar uniones e intersecciones con type aliases para modelar dominios complejos.