Da trilha de TypeScript Este conceito ainda não saiu como card do dia. ir para o card de hoje

TypeScript Tipos Intermediário

Uniões discriminadas

Uniões discriminadas (discriminated unions) são um padrão de tipagem no TypeScript que permite modelar valores que podem assumir diferentes formas, cada uma com um campo comum que as distingue. Esse campo é chamado de discriminante e geralmente é uma propriedade literal (ex.: type: 'success').

Com uniões discriminadas, o TypeScript consegue refinar o tipo dentro de blocos switch ou if, garantindo que você só acesse propriedades específicas de cada variante. Isso evita erros em tempo de execução e torna o código mais seguro e expressivo.

Além disso, ao usar switch exaustivo com um default que atribui a variável ao tipo never, o compilador acusa se você esquecer de tratar algum caso. Isso é especialmente útil para modelar estados de forma completa, como em máquinas de estado ou resultados de operações assíncronas.

Pontos-chave

  • O campo discriminante é uma propriedade literal compartilhada por todas as variantes da união.
  • Um switch sobre o discriminante permite ao TypeScript refinar o tipo em cada case.
  • O tipo never é usado para garantir exaustividade: se um caso não for tratado, o compilador reclama.
  • Uniões discriminadas são ideais para modelar estados mutuamente exclusivos, como loading | success | error.
  • Evite usar any ou as para contornar o refinamento; deixe o compilador inferir os tipos corretos.
estados.ts
// Exemplo de união discriminada para modelar estados de carregamento
type EstadoCarregamento =
  | { status: 'carregando' }
  | { status: 'sucesso'; dados: string[] }
  | { status: 'erro'; mensagem: string };

function renderizar(estado: EstadoCarregamento): string {
  switch (estado.status) {
    case 'carregando':
      return 'Carregando...';
    case 'sucesso':
      // Aqui, estado é do tipo { status: 'sucesso'; dados: string[] }
      return `Dados: ${estado.dados.join(', ')}`;
    case 'erro':
      // Aqui, estado é do tipo { status: 'erro'; mensagem: string }
      return `Erro: ${estado.mensagem}`;
    default:
      // Garante que todos os casos foram tratados
      const exaustivo: never = estado;
      return exaustivo;
  }
}

// Testando com diferentes estados
console.log(renderizar({ status: 'carregando' }));
console.log(renderizar({ status: 'sucesso', dados: ['a', 'b'] }));
console.log(renderizar({ status: 'erro', mensagem: 'Falha na rede' }));

Exercícios

  1. 1
    Fácil

    Modelando uma forma geométrica

    Crie uma união discriminada Forma que represente um círculo, um quadrado e um retângulo. Cada variante deve ter um campo tipo como discriminante e as propriedades específicas. Depois, escreva uma função area(forma: Forma): number que calcule a área usando um switch exaustivo.

    Resolver
  2. 2
    Médio

    Máquina de estados para um semáforo

    Modele o estado de um semáforo como uma união discriminada com os estados 'vermelho', 'amarelo' e 'verde'. Cada estado pode ter um tempo de duração opcional. Crie uma função proximoEstado(estado: EstadoSemaforo): EstadoSemaforo que retorna o próximo estado seguindo a ordem: vermelho → verde → amarelo → vermelho, e uma função descricao(estado: EstadoSemaforo): string que retorna uma descrição legível. Use switch exaustivo com never.

    Resolver
  3. 3
    Desafio

    Resultado de operação assíncrona com carregamento

    Modele o resultado de uma operação assíncrona que pode estar carregando, ter sucesso (com dados) ou falhar (com erro). Crie uma união discriminada Resultado<T> com os estados 'carregando', 'sucesso' e 'erro'. Implemente uma função mensagem(resultado: Resultado<string>): string que retorna uma mensagem apropriada. Além disso, crie uma função map que transforma o valor de sucesso, mantendo os outros estados inalterados. Use switch exaustivo com never em ambas as funções.

    Resolver

Continue estudando