Saltar al contenido
uniones-y-alias.ts · devschool

Uniones, intersecciones y alias de tipo en TypeScript

Lección 6 de 15 · 11 min de lectura · Actualizado el

En esta lección
  1. Tipos unión
  2. Con una unión solo puedes usar lo común
  3. Valores que pueden faltar: unión con null o undefined
  4. Uniones de literales
  5. Alias de tipo con type
  6. Uniones de objetos
  7. Intersecciones con &
  8. ¿type o interface?
  9. Arrays de uniones
  10. Errores frecuentes
  11. Resumen

Hay valores que pueden ser de más de un tipo: un código de producto que a veces es número y a veces texto, un usuario que puede no haber iniciado sesión (null), un pago que puede ser con tarjeta o con Bizum. En esta lección aprenderás a describirlos con tipos unión (A | B), a combinar tipos con intersecciones (A & B) y a poner nombre a cualquier tipo con type. Al final sabrás cuándo usar type y cuándo interface.

Tipos unión

Un tipo unión dice que un valor puede ser de uno u otro tipo. Se escribe separando los tipos con una barra vertical |, que se lee “o”:

let codigoPostal: string | number;

codigoPostal = "39008";
codigoPostal = 39008;
codigoPostal = true;
// Error: Type 'boolean' is not assignable to type 'string | number'.

Puedes unir tantos tipos como quieras, y se usan igual en variables, parámetros, propiedades y arrays:

function mostrarTalla(talla: number | string): string {
  return `Talla: ${talla}`;
}

console.log(mostrarTalla(42));
console.log(mostrarTalla("XL"));
// Talla: 42
// Talla: XL

Con una unión solo puedes usar lo común

Aquí está la clave de las uniones. Si un valor es string | number, TypeScript solo te deja hacer con él lo que se puede hacer con los dos tipos:

function formatear(valor: string | number): string {
  console.log(valor.toString()); // bien: string y number tienen toString
  return valor.toUpperCase();
}
// Error: Property 'toUpperCase' does not exist on type 'string | number'.
//          Property 'toUpperCase' does not exist on type 'number'.

Tiene sentido: si te pasan un número, toUpperCase no existe y el programa fallaría. Para usar algo propio de uno de los tipos, primero compruebas cuál es. Dentro de cada rama, TypeScript ya sabe el tipo exacto:

function formatear(valor: string | number): string {
  if (typeof valor === "string") {
    return valor.toUpperCase();   // aquí valor es string
  }
  return valor.toFixed(2);        // aquí solo puede ser number
}

console.log(formatear("rebajas"));
console.log(formatear(7.5));
// REBAJAS
// 7.50

Esta técnica se llama estrechar el tipo (narrowing): a partir de una comprobación, TypeScript reduce la unión a una de sus partes. Tiene muchas variantes (typeof, in, instanceof, comparaciones…) y la verás a fondo en Estrechar tipos. Por ahora te basta con typeof.

Nota: una unión no “convierte” nada. string | number no significa que el valor sea las dos cosas a la vez, sino que en cada momento es una de ellas y tú tienes que averiguar cuál antes de usar lo específico.

Valores que pueden faltar: unión con null o undefined

El uso más frecuente de las uniones es indicar que un valor puede no existir. Con strict activado, null y undefined no caben en un string normal, así que hay que decirlo:

let usuarioConectado: string | null = null;

function saludo(): string {
  if (usuarioConectado === null) {
    return "Hola, invitado";
  }
  return `Hola, ${usuarioConectado}`;
}

console.log(saludo());
usuarioConectado = "Marta";
console.log(saludo());
// Hola, invitado
// Hola, Marta

Los operadores ?. (encadenamiento opcional) y ?? (valor por defecto) de JavaScript encajan perfectamente con estas uniones:

function iniciales(nombre: string | undefined): string {
  return nombre?.slice(0, 2).toUpperCase() ?? "--";
}

console.log(iniciales("Santander"));
console.log(iniciales(undefined));
// SA
// --

Consejo: usa null para “no hay valor a propósito” (el usuario no ha iniciado sesión) y deja undefined para lo que simplemente no se ha dado, como un parámetro o una propiedad opcional. No es obligatorio, pero hace el código más predecible.

Uniones de literales

En Tipos básicos viste los tipos literales: tipos que admiten un único valor. Unidos con | describen un conjunto cerrado de opciones, y el editor te las sugiere al escribir:

let estado: "pendiente" | "enviado" | "entregado" = "pendiente";

estado = "enviado";
estado = "perdido";
// Error: Type '"perdido"' is not assignable to type '"pendiente" | "enviado" | "entregado"'.

También funcionan con números (1 | 2 | 3 | 4 | 5 para una valoración) y se pueden mezclar con otros tipos (number | "auto" para un ancho que es un número o la palabra "auto"). En Enums y literales comparamos estas uniones con los enum.

Alias de tipo con type

Escribir "pendiente" | "enviado" | "entregado" cada vez que lo necesitas es largo y fácil de equivocar. Un alias de tipo le da un nombre:

type EstadoPedido = "pendiente" | "enviado" | "entregado";
type Id = string | number;

function cambiarEstado(id: Id, nuevo: EstadoPedido): string {
  return `Pedido ${id} → ${nuevo}`;
}

console.log(cambiarEstado(1043, "enviado"));
console.log(cambiarEstado("P-2001", "entregado"));
// Pedido 1043 → enviado
// Pedido P-2001 → entregado

Se escribe type, el nombre en PascalCase, un = y el tipo. A diferencia de interface, un alias puede nombrar cualquier tipo:

type Precio = number;                                   // un primitivo
type Coordenada = [lat: number, lon: number];           // una tupla
type Etiquetas = string[];                              // un array
type Validador = (texto: string) => boolean;            // una función
type Usuario = { nombre: string; email: string | null }; // un objeto

const esEmail: Validador = (texto) => texto.includes("@");
const faro: Coordenada = [43.49, -3.79];

console.log(esEmail("hola@devschool.es"), faro[0]);
// true 43.49

El tipo de las funciones como Validador lo explicamos en Funciones.

Un alias es solo un nombre

Un alias no crea un tipo nuevo: es otra forma de llamar a uno que ya existe. Por eso dos alias con la misma forma son totalmente intercambiables:

type Euros = number;
type Kilos = number;

const precio: Euros = 12;
const peso: Kilos = precio; // TypeScript no protesta: ambos son number
console.log(peso);
// 12

Esto es parte del tipado estructural que viste con las interfaces: lo que cuenta es la forma, no el nombre. Ten en cuenta que el alias no te protege de mezclar euros con kilos; solo hace el código más legible.

Uniones de objetos

Las uniones brillan cuando describen datos que pueden tener formas distintas. Imagina los métodos de pago de una tienda:

type PagoTarjeta = { metodo: "tarjeta"; numero: string; titular: string };
type PagoBizum = { metodo: "bizum"; telefono: string };
type PagoEfectivo = { metodo: "efectivo" };

type Pago = PagoTarjeta | PagoBizum | PagoEfectivo;

function resumen(pago: Pago): string {
  switch (pago.metodo) {
    case "tarjeta":
      return `Tarjeta acabada en ${pago.numero.slice(-4)}`;
    case "bizum":
      return `Bizum al ${pago.telefono}`;
    case "efectivo":
      return "Pago en tienda";
  }
}

console.log(resumen({ metodo: "tarjeta", numero: "4111222233334444", titular: "Ane" }));
console.log(resumen({ metodo: "bizum", telefono: "600123456" }));
console.log(resumen({ metodo: "efectivo" }));
// Tarjeta acabada en 4444
// Bizum al 600123456
// Pago en tienda

Todas las formas comparten la propiedad metodo con un valor literal distinto. Al comprobarla en el switch, TypeScript sabe en cada case qué forma tiene el pago y te deja usar numero o telefono solo donde existen. Fuera del switch, pago.telefono sería un error, porque no todas las formas lo tienen.

Este patrón se llama unión discriminada y es uno de los más útiles de TypeScript para modelar estados (cargando, con datos, con error), mensajes o acciones. Lo estudiarás en detalle, junto con la comprobación de que no se te olvida ningún caso, en Estrechar tipos.

Intersecciones con &

Si la unión A | B significa “A o B”, la intersección A & B significa “A y B a la vez”: un objeto que tiene todas las propiedades de los dos tipos.

type ConId = { id: number };
type ConFechas = { creado: Date };
type Producto = { nombre: string; precio: number };

type ProductoGuardado = Producto & ConId & ConFechas;

const p: ProductoGuardado = {
  id: 7,
  nombre: "Esterilla",
  precio: 24.95,
  creado: new Date("2026-05-10"),
};

console.log(`#${p.id} ${p.nombre}: ${p.precio} €`);
// #7 Esterilla: 24.95 €

Es el equivalente a extends de las interfaces, pero con alias. Si falta cualquiera de las propiedades de alguna de las partes, TypeScript avisa.

Cuidado: si intersectas tipos incompatibles, el resultado es imposible. string & number es never: ningún valor puede ser a la vez texto y número. Si de repente ves never donde no lo esperabas, revisa tus intersecciones.

¿type o interface?

Para describir objetos, los dos sirven y la mayoría de las veces el resultado es idéntico. Estas son las diferencias reales:

interfacetype
ObjetosSíSí
Uniones, tuplas, primitivos, funciones sueltasNoSí
AmpliarextendsIntersección &
Declararla dos vecesSe fusionanError: Duplicate identifier
Una clase la cumple con implementsSíSí, si es un objeto
Mensajes de errorMuestran el nombreA veces muestran el tipo desplegado

Con estas diferencias, una regla sencilla que siguen muchos equipos:

  • interface para la forma de los objetos de tu aplicación (Usuario, Pedido, Producto).
  • type para uniones, literales, tuplas, funciones y combinaciones (EstadoPedido, Pago, Coordenada, Validador).

Lo importante es ser coherente dentro del proyecto. Si el equipo usa siempre type, también está bien.

interface Cliente {
  nombre: string;
  email: string;
}

type Nivel = "basico" | "premium";

interface ClienteFiel extends Cliente {
  nivel: Nivel;
  puntos: number;
}

const ane: ClienteFiel = { nombre: "Ane", email: "ane@correo.es", nivel: "premium", puntos: 320 };
console.log(`${ane.nombre} (${ane.nivel}): ${ane.puntos} puntos`);
// Ane (premium): 320 puntos

Arrays de uniones

Recuerda de Arrays y tuplas que los paréntesis cambian el significado. Con un alias se evita la confusión:

type Celda = number | null;
const fila: Celda[] = [3, null, 7, null];

const sumaFila = fila.reduce<number>((suma, c) => suma + (c ?? 0), 0);
console.log(sumaFila);
// 10

Celda[] es mucho más fácil de leer que (number | null)[].

Errores frecuentes

  • Usar un método de un solo tipo sobre la unión: con string | number no puedes llamar a toUpperCase() sin comprobar antes con typeof.
  • Olvidar null en la unión: si un valor puede faltar, decláralo Tipo | null; si no, strict no te dejará asignarle null.
  • Confundir | con &: A | B es “uno de los dos”; A & B es “los dos a la vez”. En objetos, la intersección tiene más propiedades, no menos.
  • Pensar que un alias crea un tipo distinto: type Euros = number sigue siendo number y se mezcla con cualquier otro number.
  • Escribir type sin =: es type Id = string | number, no type Id { ... }. Las llaves sin = son de interface.
  • Abusar de uniones enormes de primitivos: string | number | boolean | null en un parámetro suele indicar que la función hace demasiadas cosas.
  • Acceder a una propiedad que no está en todas las formas de una unión de objetos: comprueba antes el campo común (metodo, tipo, estado).

Resumen

ConceptoEjemplo
Uniónstring | number
Solo lo comúnCon una unión, solo métodos que tengan todos los tipos
Estrechar con typeofif (typeof x === "string") { ... }
Puede faltarstring | null, con ?. y ??
Unión de literales"pendiente" | "enviado" | "entregado"
Aliastype Id = string | number
Alias de cualquier tipoTuplas, funciones, objetos, arrays
Unión de objetostype Pago = PagoTarjeta | PagoBizum con campo común
Interseccióntype Guardado = Producto & ConId
interface frente a typeinterface para objetos; type para el resto

En la siguiente lección aprenderás a tipar parámetros, valores de retorno y callbacks: Funciones.

Pon a prueba lo que has aprendido

[TypeScript] ¿Por qué da error toUpperCase en esta función?
function formatear(valor: string | number): string {
  return valor.toUpperCase();
}

[TypeScript] ¿Qué hace TypeScript con la última línea?
type Euros = number;
type Kilos = number;
const precio: Euros = 12;
const peso: Kilos = precio;

[TypeScript] Si Producto = { nombre: string } y ConId = { id: number }, ¿cómo debe ser un objeto de tipo Producto & ConId?

[TypeScript] En una unión discriminada Pago (con campo metodo literal), ¿por qué puedes usar pago.telefono dentro de case "bizum"?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.