Un tipo unión enumera alternativas con |: un valor de tipo string | number es o un string o un número. Las uniones son la forma en que TypeScript describe valores que pueden tener legítimamente más de una forma, y el compilador te obliga a comprobar cuál tienes antes de usar nada específico de ella.
Dentro de cada rama de la comprobación con typeof, id tiene un solo tipo. Ese paso se llama estrechamiento, y es lo que hace prácticas las uniones.
Solo se permiten los miembros comunes
Antes de estrechar, solo puedes usar lo que admiten todos los miembros de la unión. toString() existe en strings y en números, así que está bien; toUpperCase() solo existe en los strings:
index.ts(3,16): error TS2339: Property 'toUpperCase' does not exist on type 'string | number'.
Property 'toUpperCase' does not exist on type 'number'.
La segunda línea nombra el miembro al que le falta la propiedad. La misma regla se aplica en el otro sentido: un valor string | number no se puede pasar a un parámetro de tipo string (TS2345), porque podría ser un número. Una unión acepta más valores y, a cambio, te deja hacer menos con ellos hasta que compruebas.
Estrechar una unión
El estrechamiento usa comprobaciones normales de JavaScript. TypeScript sigue el flujo de control y va quitando miembros a medida que se descartan, así que tras la última comprobación solo queda uno:
| Comprobación | Estrecha | Sirve para |
|---|---|---|
typeof x === "string" | al primitivo | string, number, boolean, bigint, symbol, undefined, function |
x === null, x === "a" | al valor comparado | null, undefined, miembros literales |
Array.isArray(x) | al miembro array | arrays |
x instanceof Date | a la clase | instancias de clases |
"meow" in x | a los miembros que tienen la propiedad | tipos objeto |
x.kind === "circle" | al miembro con esa etiqueta | uniones discriminadas |
isCat(x) (devuelve x is Cat) | a lo que dice la función | cualquier cosa, lógica propia |
La lista completa de formas de estrechar está en la página de estrechamiento de tipos.
Uniones de tipos literales
Una unión de valores literales es un conjunto cerrado de valores permitidos. Es la unión más habitual en código real:
type Status = "idle" | "loading" | "success" | "error";
type Dice = 1 | 2 | 3 | 4 | 5 | 6;
type Toggle = "on" | "off" | boolean; // boolean is itself true | false
let current: Status = "idle";
current = "loading"; // fine
current = "finished"; // error TS2322: Type '"finished"' is not assignable to type 'Status'.
Comparar con un literal estrecha: tras if (current === "error"), la rama else sabe que current es uno de los otros tres. Las uniones de literales sustituyen a los enums en muchos proyectos; consulta tipos literales para ver as const y cómo derivar una unión así de un array.
Uniones de tipos objeto
Cuando los miembros son tipos objeto, las propiedades que comparten todos están disponibles directamente. Para las demás, comprueba que la propiedad existe con in:
Para uniones de varias formas de objeto, el patrón más limpio es una propiedad literal compartida como kind: "cat" / kind: "fish". Comprobar esa propiedad estrecha todo el objeto, y un switch sobre ella se puede comprobar de forma exhaustiva. Ese patrón es una unión discriminada.
Arrays y uniones
Dónde van los paréntesis cambia por completo el significado:
| Tipo | Significa | Valor de ejemplo |
|---|---|---|
(string | number)[] | un array cuyos elementos son cada uno un string o un número | [1, "two", 3] |
string[] | number[] | un array solo de strings, o un array solo de números | ["a", "b"] |
string | number[] | un string, o un array de números (| tiene menos precedencia que []) | "text" |
Al recorrer un (string | number)[], cada elemento es la unión y necesita estrecharse, como en el callback de reduce de arriba. Métodos como map y filter también funcionan sobre un string[] | number[], y el callback recibe string | number.
Uniones con null y undefined
La unión más habitual de todas es «un valor o nada»: string | null, User | undefined. Es lo que devuelven Array.prototype.find y Map.prototype.get, y una propiedad opcional name?: string se lee como string | undefined. Manejarlas con ?., ?? y comprobaciones de null tiene su propia página: null y undefined.
Para quitar miembros de una unión existente a nivel de tipos, usa las utilidades integradas: Exclude<"a" | "b" | "c", "a"> es "b" | "c", y NonNullable<string | null> es string.
Preguntas frecuentes
¿Qué es un tipo unión en TypeScript?
Un tipo formado por varias alternativas unidas con |. Un valor de tipo string | number puede ser un string o un número. El compilador solo te deja usar lo que todos los miembros tienen en común hasta que estrechas el valor a un miembro con una comprobación como typeof value === "string".
¿Por qué TypeScript dice que una propiedad no existe en un tipo unión?
Porque al menos un miembro de la unión no la tiene. El error TS2339, por ejemplo Property 'toUpperCase' does not exist on type 'string | number', significa que el valor podría ser un número, que no tiene toUpperCase. Estrecha primero (typeof, in, Array.isArray, instanceof o una comprobación del discriminante) y luego usa la propiedad específica del miembro.
¿Cómo declaro un array que contiene más de un tipo?
Pon la unión entre paréntesis: (string | number)[] o Array<string | number>, donde cada elemento puede ser de cualquiera de los dos tipos. string[] | number[] es distinto: el array entero es todo strings o todo números. Sin paréntesis, string | number[] significa un string o un array de números.
¿Qué diferencia hay entre un tipo unión y uno intersección?
Una unión A | B es un valor que es uno de los tipos, así que solo puedes usar lo que comparten. Una intersección A & B es un valor que es ambos a la vez, así que tiene todos los miembros de los dos. Con tipos objeto, A | B acepta más valores y A & B exige más propiedades.
¿Cómo compruebo de qué tipo es un valor de una unión?
Usa una comprobación en tiempo de ejecución que TypeScript entienda: typeof x === "string" para primitivos, Array.isArray(x) para arrays, x instanceof Date para clases, "prop" in x para formas de objeto, o x.kind === "circle" cuando los miembros comparten una etiqueta literal. Para lógica propia, escribe una función type guard que devuelva x is T.