Um union type lista alternativas com |: um valor do tipo string | number é uma string ou um número. Unions são o jeito de o TypeScript descrever valores que podem legitimamente ter mais de uma forma, e o compilador obriga você a verificar qual forma tem antes de usar algo específico dela.
Dentro de cada ramo da verificação typeof, id tem um único tipo. Esse passo se chama narrowing, e é ele que torna as unions práticas.
Só os membros em comum são permitidos
Antes do narrowing, você só pode usar o que todos os membros da union suportam. toString() existe em strings e em números, então tudo bem; toUpperCase() só existe em strings:
index.ts(3,16): error TS2339: Property 'toUpperCase' does not exist on type 'string | number'.
Property 'toUpperCase' does not exist on type 'number'.
A segunda linha indica o membro que não tem a propriedade. A mesma regra vale no sentido contrário: um valor string | number não pode ser passado para um parâmetro tipado como string (TS2345), porque pode ser um número. Uma union aceita mais valores e, em troca, deixa você fazer menos com eles até verificar.
Narrowing de uma union
O narrowing usa verificações comuns de JavaScript. O TypeScript acompanha o fluxo de controle e remove membros à medida que são descartados, então depois da última verificação sobra só um membro:
| Verificação | Estreita para | Serve para |
|---|---|---|
typeof x === "string" | o primitivo | string, number, boolean, bigint, symbol, undefined, function |
x === null, x === "a" | o valor comparado | null, undefined, membros literais |
Array.isArray(x) | o membro array | arrays |
x instanceof Date | a classe | instâncias de classes |
"meow" in x | os membros que têm a propriedade | tipos de objeto |
x.kind === "circle" | o membro com essa tag | discriminated unions |
isCat(x) (retorna x is Cat) | o que a função diz | qualquer coisa, lógica própria |
A lista completa de formas de narrowing está na página sobre type narrowing.
Unions de literal types
Uma union de valores literais é um conjunto fechado de valores permitidos. É a union mais comum em 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 com um literal faz narrowing: depois de if (current === "error"), o else sabe que current é um dos outros três. Unions de literais substituem enums em muitas bases de código; veja literal types para o as const e como derivar uma union dessas a partir de um array.
Unions de tipos de objeto
Quando os membros são tipos de objeto, as propriedades que todos compartilham ficam disponíveis diretamente. Para o resto, verifique se a propriedade existe com in:
Para unions de vários formatos de objeto, o padrão mais limpo é uma propriedade literal compartilhada, como kind: "cat" / kind: "fish". Verificar essa única propriedade estreita o objeto inteiro, e um switch sobre ela pode ter a exaustividade verificada. Esse padrão é uma discriminated union.
Arrays e unions
Onde ficam os parênteses muda completamente o significado:
| Tipo | Significa | Exemplo de valor |
|---|---|---|
(string | number)[] | um array em que cada elemento é uma string ou um número | [1, "two", 3] |
string[] | number[] | um array só de strings, ou um array só de números | ["a", "b"] |
string | number[] | uma string, ou um array de números (| tem precedência menor que []) | "text" |
Ao percorrer um (string | number)[], cada elemento é a union e precisa de narrowing, como no callback do reduce acima. Métodos como map e filter também funcionam em um string[] | number[], com o callback recebendo string | number.
Unions com null e undefined
A union mais comum de todas é "um valor ou nada": string | null, User | undefined. É o que Array.prototype.find e Map.prototype.get retornam, e uma propriedade opcional name?: string é lida como string | undefined. Tratar esses casos com ?., ?? e verificações de null tem uma página própria: null e undefined.
Para remover membros de uma union existente no nível dos tipos, use os utilitários nativos: Exclude<"a" | "b" | "c", "a"> é "b" | "c", e NonNullable<string | null> é string.
Perguntas frequentes
O que é um union type no TypeScript?
Um tipo formado por várias alternativas unidas com |. Um valor do tipo string | number pode ser uma string ou um número. O compilador só deixa você usar o que todos os membros têm em comum até você fazer o narrowing do valor para um membro com uma verificação como typeof value === "string".
Por que o TypeScript diz que uma propriedade não existe em um union type?
Porque pelo menos um membro da union não a tem. O erro TS2339, por exemplo Property 'toUpperCase' does not exist on type 'string | number', significa que o valor pode ser um número, que não tem toUpperCase. Faça o narrowing antes (typeof, in, Array.isArray, instanceof ou uma verificação de discriminante) e depois use a propriedade específica do membro.
Como declarar um array que guarda mais de um tipo?
Coloque a union entre parênteses: (string | number)[] ou Array<string | number>, em que cada elemento pode ser de qualquer um dos tipos. string[] | number[] é diferente: o array inteiro é só de strings ou só de números. Sem parênteses, string | number[] significa uma string ou um array de números.
Qual é a diferença entre um union type e um intersection type?
Uma union A | B é um valor que é um dos tipos, então você só pode usar o que eles têm em comum. Uma intersection A & B é um valor que é os dois ao mesmo tempo, então tem todos os membros de ambos. Para tipos de objeto, A | B aceita mais valores e A & B exige mais propriedades.
Como verificar de qual tipo é um valor de union?
Use uma verificação em tempo de execução que o TypeScript entende: typeof x === "string" para primitivos, Array.isArray(x) para arrays, x instanceof Date para classes, "prop" in x para formatos de objeto, ou x.kind === "circle" quando os membros compartilham uma tag literal. Para lógica própria, escreva uma função type guard que retorna x is T.