Tipos De Variações - Tipos de Variações - Match up
Tipos de Variações - Match up

Variações em desenvolvimento web: o que funciona na prática

Você já se deparou com um erro de tipagem que parecia impossível? Aquilo que deveria funcionar simplesmente não compila e a mensagem de erro não ajuda em nada. É exatamente sobre isso que vamos falar aqui. Tipos de variações é um conceito que aparece em vários lugares no desenvolvimento moderno, especialmente quando trabalhamos com linguagens estáticas como TypeScript ou quando lidamos com APIs que retornam dados imprevisíveis. A gente precisa entender como elas funcionam antes de confiar cegamente nelas.

O que são tipos de variações no dia a dia

No fundo, variações são simplesmente formas diferentes que um dado pode assumir. Um campo pode ser string, número, array vazio, null, ou um objeto com propriedades específicas. A questão é que a maioria dos tutoriais ensina a teoria de forma muito abstrata. Na prática, você lida com dados que chegam de endpoints que mudam sem aviso, formulários com campos condicionais, e respostas de API que ora vêm completas ora vêm truncadas. O tipo de variação é a representação disso no seu código. Eu já perdi uma tarde inteira debugando um problema que parecia simples. Tinha um formulário onde certos campos apareciam dependendo do valor de um select. A lógica estava certa no papel, mas o TypeScript reclamava que uma propriedade não existia. O problema era que eu estava usando discriminação de união de forma errada. Em vez de usar uma chave discriminadora clara, eu estava verificando propriedades que poderiam não existir. A solução foi simples: criar um tipo discriminado com uma propriedade obrigatória que indicava qual variante estava ativa, algo como type Variant A { kind: 'A'; value: string } | type Variant B { kind: 'B'; count: number }. Isso resolveu o problema imediatamente porque o compilador passava a entender exatamente qual tipo estava em uso em cada branch.

Métodos de implementação e exemplos práticos

A maneira mais comum de lidar com tipos de variações é através de union types. Você define que uma variável pode assumir múltiplos formatos e deixa o TypeScript trabalhar a favor de você. Mas tem um detalhe importante que poucos mencionam: a ordem dos tipos na união importa. Se você colocar string antes de number, e seu dado for um número que pode ser convertido para string, o inferidor de tipos vai escolher a primeira opção e você terá bugs sutis. A ordem correta sempre deve ir do mais específico para o mais genérico. Vamos a um exemplo real. Digamos que você está construindo um sistema de notificações onde uma notificação pode ser um texto simples, uma imagem, ou um link. A definição would look like this:

type Notification = { type: 'text'; content: string } | { type: 'image'; src: string; alt: string } | { type: 'link'; href: string; label: string } Percebeu o que fiz aqui? Adicionei uma propriedade type em cada variante. Isso é called discriminated union e é fundamental para que o TypeScript entenda qual variant você está lidando em cada momento. Sem essa propriedade discriminadora, o compilador não consegue narrowing correto e você acaba tendo que fazer casts manuais, o que é sinal de que algo está errado no design do tipo.

Outro padrão útil é o use of readonly arrays para variações que representam listas fixas de opções. Por exemplo, se você tem um status de pedido que só pode ser 'pending', 'shipped', ou 'delivered', definir isso como type OrderStatus = readonly ['pending', 'shipped', 'delivered'] garante que ninguém adicione um status novo por engano. Isso é especialmente útil em sistemas legado onde tipos mutáveis causam efeitos colaterais imprevisíveis.

Pegadinhas e casos onde isso falha

Nem tudo são flores. Tipos de variações têm limitações sérias que você precisa conhecer antes de implementá-los em produção. O primeiro problema é performance. Quanto mais variantes você tiver, mais tempo o TypeScript leva para fazer type checking. Eu vi projetos onde o build demorava de 30 segundos para 4 minutos simplesmente porque alguém criou uma union type com mais de cinquenta variantes. Se você está nessa situação, considere split em múltiplos arquivos ou usar template literal types para gerar variantes automaticamente. O segundo problema é manutenibilidade. Cada nova variante que você adiciona precisa ser tratada em todos os lugares onde a union type é usada. Se você tem cinquenta componentes que fazem pattern matching sobre uma union com cinco variantes e decide adicionar uma sexta, precisa atualizar todos esses cinquenta lugares. Isso é inevitável e é o preço da segurança de tipos. A alternativa seria usar types dinâmicos, mas aí você perde a verificação em tempo de compilação e troca um problema por outro.

Existe ainda o problema do exhaustive checking. O TypeScript não te obriga a tratar todas as variantes de uma union. Você pode escrever um switch que trata quatro de cinco casos e o código compila sem warnings. Para contornar isso, existe uma técnica chamada exhaustiveness checking que usa um padrão this: function assertNever(x: never): never { throw new Error('Unexpected value'); }

👉 Clique no botão abaixo para saber mais sobre o assunto!

function handleNotification(notification: Notification): string { switch (notification.type) {

case 'text': return notification.content; case 'image': return notification.alt;

case 'link': return notification.label; default: return assertNever(notification);

} }

Se você adicionar uma nova variante e esquecer de atualizar a função, o default vai passar o valor para assertNever, que espera um never. Como o tipo agora não é mais never, o TypeScript vai reclamar. Essa técnica é simples mas extremamente eficaz para evitar bugs de variantes órfãs.

Alternativas quando tipos de variações não são suficientes

Às vezes, a simplicidade dos union types não aguenta a complexidade do problema. Nesses casos, existem alternativas. Uma delas é usar o padrão Adapter, onde você normaliza dados de diferentes formatos para um formato único antes de processá-los. Outra opção é o uso de branded types, que criam subtipos distinguíveis semanticamente sem alterar a estrutura subjacente. Por exemplo, type UserId = string & { __brand: 'userId' } permite que você diferencie um string que é ID de um string que é nome, mesmo que ambos sejam strings no fundo. Se o seu problema envolve variações que mudam frequentemente ou dependem de configuração do usuário, considere usar schema validation com bibliotecas como Zod ou Yup. Elas permitem validar dados em tempo de execução e gerar tipos automaticamente a partir dos schemas. O trade-off é que você perde parte da verificação em tempo de compilação, mas ganha flexibilidade que union types estáticos não oferecem.

O que eu recomendo na maioria dos casos é começar simples. Defina suas variantes com discriminação clara, use exhaustive checking para capturar variantes esquecidas, e só adicione complexidade quando realmente precisar. A tentação de criar hierarquias de tipos elaboradas é forte, mas na prática ela raramente vale o custo de manutenção. Tipos de variações bem desenhados são aqueles que você esquece que existem porque simplesmente funcionam.