Union Types no TypeScript: Guia Prático
Union types são uma das ferramentas mais poderosas do TypeScript. Com o operador |, você combina tipos e cria código flexível sem abrir mão da segurança de tipagem.
Por que isso é importante
Union Types no TypeScript: Guia Prático. Union types são uma das ferramentas mais poderosas do TypeScript. Com o operador |, você combina tipos e cria código flexível sem abrir mão da segurança de tipagem. Aprenda narrowing, type guards e discriminated unions na prática.
O que são Union Types e quando usar
Union type nada mais é do que dizer: essa variável aceita tipo A ou tipo B. A sintaxe usa o operador | (pipe). Simples assim. Você combina dois ou mais tipos, e o TypeScript garante que só operações válidas para todos os tipos da union passem pelo compilador.
O uso mais direto é quando uma função recebe parâmetros que podem ter formatos diferentes. Por exemplo, um ID que pode ser string ou number. Ou o status de um pedido que só aceita valores específicos. Union types cobrem esses cenários sem nenhuma gambiarra.
Dá pra usar union types em variáveis, parâmetros de função, retornos, propriedades de objetos e até dentro de generics. A flexibilidade é enorme. Mas o poder de verdade aparece quando você combina union types com narrowing -- o processo de afunilar o tipo dentro de um bloco de código.
// Union type simples
type ID = string | number;
let userId: ID = "abc123";
userId = 42; // Tudo certo, aceita os dois
// Union com literais
type Status = "ativo" | "inativo" | "pendente";
let statusUsuario: Status = "ativo"; // OK
// statusUsuario = "banido"; // Erro! Não faz parte da union
Passo a passo: do básico ao avançado
- Defina a union com o operador | — Combine dois ou mais tipos:
type Resultado = string | number. Pode ser com types primitivos, interfaces, objetos literais ou até outras unions. - Use narrowing para acessar propriedades específicas — Dentro de um
ifouswitch, o TypeScript afunila automaticamente o tipo. Depois do check, você acessa métodos exclusivos de cada tipo sem erro. - Crie type guards customizados — Quando o narrowing automático não resolve, crie funções com
isno retorno:function isString(val: unknown): val is string. Isso dá controle total. - Aplique discriminated unions para objetos complexos — Adicione uma propriedade literal (como
type: "sucesso") em cada variante. O TypeScript usa essa prop pra diferenciar os tipos automaticamente. - Combine com Generics para reutilização — Union types dentro de generics criam estruturas como
Result<T> = Success<T> | Error. Padrão usado em APIs, hooks e serviços.
Exemplos práticos: narrowing e discriminated unions
Narrowing com typeof
O narrowing mais básico usa typeof. O TypeScript entende que, dentro do bloco if (typeof x === "string"), a variável é string. Fora do bloco, continua sendo a union completa.
function formatarID(id: string | number): string {
if (typeof id === "string") {
return id.toUpperCase(); // TypeScript sabe que é string aqui
}
return id.toFixed(0); // Aqui sabe que é number
}
console.log(formatarID("abc")); // "ABC"
console.log(formatarID(42)); // "42"
Discriminated Unions com objetos
Galera, discriminated unions são o padrão mais usado em projetos reais. Você coloca uma propriedade literal que serve como "etiqueta" pra cada variante. O TypeScript usa essa etiqueta pra inferir o tipo exato dentro de um switch ou if.
type Sucesso = {
status: "sucesso";
dados: string[];
};
type Falha = {
status: "erro";
mensagem: string;
};
type Resposta = Sucesso | Falha;
function tratarResposta(res: Resposta) {
switch (res.status) {
case "sucesso":
// TypeScript sabe que res é Sucesso aqui
console.log(res.dados.join(", "));
break;
case "erro":
// TypeScript sabe que res é Falha aqui
console.log(`Erro: ${res.mensagem}`);
break;
}
}
Type Guard customizado
Quando trabalha com classes ou objetos sem propriedade discriminante, dá pra criar type guards com a keyword is. A função retorna um booleano, mas o TypeScript usa a anotação pra refinar o tipo.
interface Gato {
miar(): void;
}
interface Cachorro {
latir(): void;
}
type Pet = Gato | Cachorro;
function isGato(pet: Pet): pet is Gato {
return (pet as Gato).miar !== undefined;
}
function fazerBarulho(pet: Pet) {
if (isGato(pet)) {
pet.miar(); // TypeScript sabe que é Gato
} else {
pet.latir(); // TypeScript sabe que é Cachorro
}
}
Erros comuns com Union Types
Cuidado com esses deslizes
Acessar propriedade sem narrowing: se a variável é string | number, você não pode chamar .toUpperCase() direto. O TypeScript exige que você afunile o tipo antes.
Confundir union com intersection: A | B significa A ou B. A & B significa A e B ao mesmo tempo. A diferença é enorme e gera bugs difíceis de rastrear quando trocadas.
Esquecer de tratar todos os casos: quando usa discriminated unions, cada variante precisa ser coberta. Se adicionar um novo status e esquecer o case no switch, vai ter surpresa em produção.
Usar any ao invés de union: se o parâmetro aceita string ou number, declare string | number. Colocar any desliga toda proteção do compilador -- perde o sentido de usar TypeScript.
Não usar exhaustive check: no final do switch, adicione um default com never pra garantir que novas variantes sejam tratadas em tempo de compilação.
Checklist: Union Types bem aplicados
Checklist de Union Types
- Usei | para combinar tipos ao invés de any
- Apliquei narrowing com typeof, instanceof ou in antes de acessar propriedades específicas
- Criei type guards customizados para objetos sem propriedade discriminante
- Usei propriedade literal como discriminante em unions de objetos
- Cobri todos os casos da union no switch/if
- Adicionei exhaustive check com never no default do switch
- Combinei unions com generics para padrões reutilizáveis
Domine TypeScript na prática
Union types são só o começo. No CrazyStack, você aprende TypeScript construindo projetos reais com Node.js e React -- cada conceito aplicado em código que vai pro ar. Chega de tutorial solto: monte seu projeto do zero ao deploy com tipagem forte do início ao fim.