TypeScript

Union Types, Intersection Types e Type Guards em TypeScript: Do Básico ao Avançado

10 min de leitura

Union Types, Intersection Types e Type Guards em TypeScript: Do Básico ao Avançado

Union Types: Flexibilidade com Segurança Union Types permitem que uma variável ou parâmetro aceite múltiplos tipos diferentes. Em vez de forçar um único tipo, você declara que um valor pode ser de tipo A ou tipo B ou tipo C. Isso é fundamental quando você precisa trabalhar com dados que podem variar em sua natureza, mas ainda quer manter a segurança de tipos que TypeScript oferece. A sintaxe é simples: use o operador pipe ( ) entre os tipos. O compilador TypeScript garantirá que você só acesse propriedades e métodos comuns a todos os tipos na union, ou então execute verificações de tipo antes de usar funcionalidades específicas. Sem essa segurança, você poderia tentar chamar um método que existe apenas em um dos tipos, causando um erro em tempo de execução. Unions também funcionam com objetos complexos. Imagine um sistema que retorna dados diferentes baseado no tipo de requisição: Nome: ${pessoa.nome} Email: ${pessoa.email} Intersection Types: Combinação de Características Intersection Types

<h2>Union Types: Flexibilidade com Segurança</h2>

<p>Union Types permitem que uma variável ou parâmetro aceite múltiplos tipos diferentes. Em vez de forçar um único tipo, você declara que um valor pode ser de tipo A <strong>ou</strong> tipo B <strong>ou</strong> tipo C. Isso é fundamental quando você precisa trabalhar com dados que podem variar em sua natureza, mas ainda quer manter a segurança de tipos que TypeScript oferece.</p>

<p>A sintaxe é simples: use o operador pipe (<code> | </code>) entre os tipos. O compilador TypeScript garantirá que você só acesse propriedades e métodos comuns a todos os tipos na union, ou então execute verificações de tipo antes de usar funcionalidades específicas. Sem essa segurança, você poderia tentar chamar um método que existe apenas em um dos tipos, causando um erro em tempo de execução.</p> <pre><code class="language-typescript">type StatusResponse = &#039;sucesso&#039; | &#039;erro&#039; | &#039;pendente&#039;;

function procesarStatus(status: StatusResponse): void {

if (status === &#039;sucesso&#039;) {

console.log(&#039;Operação completada com sucesso&#039;);

} else if (status === &#039;erro&#039;) {

console.log(&#039;Ocorreu um erro durante a operação&#039;);

} else {

console.log(&#039;Ainda processando...&#039;);

}

}

procesarStatus(&#039;sucesso&#039;); // ✓ Válido

procesarStatus(&#039;invalido&#039;); // ✗ Erro de compilação</code></pre>

<p>Unions também funcionam com objetos complexos. Imagine um sistema que retorna dados diferentes baseado no tipo de requisição:</p>

<pre><code class="language-typescript">interface Usuario {

tipo: &#039;usuario&#039;;

nome: string;

email: string;

}

interface Administrador {

tipo: &#039;admin&#039;;

nome: string;

email: string;

permissoes: string[];

}

type Pessoa = Usuario | Administrador;

function exibirPessoa(pessoa: Pessoa): void {

console.log(Nome: ${pessoa.nome});

console.log(Email: ${pessoa.email});

// Propriedade &#039;permissoes&#039; não existe em todas as unions

// console.log(pessoa.permissoes); // ✗ Erro

}</code></pre>

<h2>Intersection Types: Combinação de Características</h2>

<p>Intersection Types fazem o oposto das unions: em vez de &quot;ou&quot;, temos &quot;e&quot;. Um tipo intersection combina múltiplos tipos em um único tipo que possui todas as propriedades e métodos de cada um dos tipos combinados. Use o operador <code>&amp;</code> para declarar intersections.</p>

<p>Isso é útil quando você precisa que um objeto cumpra contratos múltiplos simultaneamente. Por exemplo, um usuário que deve ser tanto uma Pessoa quanto um Funcionário. Em vez de duplicar código, você cria uma intersection que herda características de ambas as interfaces.</p>

<pre><code class="language-typescript">interface Pessoa {

nome: string;

idade: number;

}

interface Funcionario {

matricula: string;

salario: number;

departamento: string;

}

type PessoaFuncionario = Pessoa &amp; Funcionario;

const joao: PessoaFuncionario = {

nome: &#039;João Silva&#039;,

idade: 35,

matricula: &#039;EMP001&#039;,

salario: 5000,

departamento: &#039;TI&#039;

};

console.log(${joao.nome} trabalha em ${joao.departamento});</code></pre>

<p>Intersections são particularmente poderosas ao trabalhar com mixins e composição. Considere um cenário onde você precisa estender funcionalidade de uma classe base com comportamentos adicionais:</p>

<pre><code class="language-typescript">interface Auditável {

criadoEm: Date;

atualizadoEm: Date;

criadoPor: string;

}

interface Deletável {

deletadoEm?: Date;

deletadoPor?: string;

}

interface Produto extends Auditável, Deletável {

id: number;

nome: string;

preco: number;

}

const produto: Produto = {

id: 1,

nome: &#039;Notebook&#039;,

preco: 3500,

criadoEm: new Date(&#039;2024-01-01&#039;),

atualizadoEm: new Date(&#039;2024-01-15&#039;),

criadoPor: &#039;admin&#039;,

deletadoEm: undefined,

deletadoPor: undefined

};</code></pre>

<h2>Type Guards: Garantindo Segurança em Runtime</h2>

<p>Type Guards são técnicas que permitem ao TypeScript (e ao seu código) identificar o tipo exato de uma variável em um determinado ponto. Quando você tem uma union de tipos, o compilador fica conservador: só permite operações que são seguras para <strong>todos</strong> os tipos da union. Type guards resolvem isso reduzindo o escopo de tipos possíveis através de verificações explícitas.</p>

<h3>Verificação com typeof</h3>

<p>O <code>typeof</code> operator é ideal para diferenciar tipos primitivos. Use-o quando sua union contém strings, números, booleans ou funções:</p>

<pre><code class="language-typescript">type Valor = string | number | boolean;

function procesarValor(valor: Valor): void {

if (typeof valor === &#039;string&#039;) {

console.log(String em maiúscula: ${valor.toUpperCase()});

} else if (typeof valor === &#039;number&#039;) {

console.log(Número dobrado: ${valor * 2});

} else if (typeof valor === &#039;boolean&#039;) {

console.log(Booleano invertido: ${!valor});

}

}

procesarValor(&#039;hello&#039;); // String em maiúscula: HELLO

procesarValor(42); // Número dobrado: 84

procesarValor(true); // Booleano invertido: false</code></pre>

<h3>Verificação com instanceof</h3>

<p>Use <code>instanceof</code> para verificar se um valor é instância de uma classe. Isso é essencial ao trabalhar com classes específicas em uma union:</p>

<pre><code class="language-typescript">class Cachorro {

latir(): string {

return &#039;Au au!&#039;;

}

}

class Gato {

miar(): string {

return &#039;Miau!&#039;;

}

}

type Animal = Cachorro | Gato;

function fazerSom(animal: Animal): void {

if (animal instanceof Cachorro) {

console.log(animal.latir());

} else if (animal instanceof Gato) {

console.log(animal.miar());

}

}

fazerSom(new Cachorro()); // Au au!

fazerSom(new Gato()); // Miau!</code></pre>

<h3>Verificação com Propriedades Discriminantes</h3>

<p>Quando você tem interfaces com uma propriedade comum que diferencia seus tipos (chamada discriminante), use-a para type narrowing. Essa é a abordagem mais elegante e performática:</p>

<pre><code class="language-typescript">interface Sucesso {

tipo: &#039;sucesso&#039;;

dados: unknown;

mensagem: string;

}

interface Falha {

tipo: &#039;falha&#039;;

erro: Error;

codigo: number;

}

type Resposta = Sucesso | Falha;

function tratarResposta(resposta: Resposta): void {

if (resposta.tipo === &#039;sucesso&#039;) {

console.log(Sucesso: ${resposta.mensagem});

console.log(Dados: ${JSON.stringify(resposta.dados)});

} else {

console.log(Erro ${resposta.codigo}: ${resposta.erro.message});

}

}

const respostaBom: Resposta = {

tipo: &#039;sucesso&#039;,

dados: { id: 1, nome: &#039;Test&#039; },

mensagem: &#039;Operação concluída&#039;

};

tratarResposta(respostaBom);</code></pre>

<h3>Type Predicates: Type Guards Customizados</h3>

<p>Quando verificações simples não são suficientes, crie funções que retornam type predicates. A sintaxe <code>is</code> informa ao TypeScript que a função foi executada com sucesso, o tipo foi confirmado:</p>

<pre><code class="language-typescript">interface Configuracao {

host: string;

porta: number;

ssl?: boolean;

}

interface Credenciais {

usuario: string;

senha: string;

}

type ConfigOuCredenciais = Configuracao | Credenciais;

function ehConfiguracao(obj: ConfigOuCredenciais): obj is Configuracao {

return &#039;host&#039; in obj &amp;&amp; &#039;porta&#039; in obj;

}

function ehCredenciais(obj: ConfigOuCredenciais): obj is Credenciais {

return &#039;usuario&#039; in obj &amp;&amp; &#039;senha&#039; in obj;

}

function conectar(config: ConfigOuCredenciais): void {

if (ehConfiguracao(config)) {

console.log(Conectando em ${config.host}:${config.porta});

} else if (ehCredenciais(config)) {

console.log(Autenticando usuário: ${config.usuario});

}

}

const config: ConfigOuCredenciais = {

host: &#039;localhost&#039;,

porta: 3000,

ssl: true

};

conectar(config);</code></pre>

<h2>Combinando Union, Intersection e Type Guards em Casos Práticos</h2>

<p>A verdadeira maestria vem quando você combina essas três técnicas em um cenário real. Considere um sistema de processamento de eventos que precisa lidar com diferentes tipos de eventos, cada um com suas próprias propriedades:</p>

<pre><code class="language-typescript">// Definindo eventos diferentes como union

type Evento = EventoUsuario | EventoProduto | EventoPagamento;

interface EventoUsuario {

tipo: &#039;usuario&#039;;

acao: &#039;criacao&#039; | &#039;atualizacao&#039; | &#039;delecao&#039;;

usuarioId: number;

timestamp: Date;

}

interface EventoProduto {

tipo: &#039;produto&#039;;

acao: &#039;criacao&#039; | &#039;atualizacao&#039; | &#039;delecao&#039;;

produtoId: number;

estoque: number;

timestamp: Date;

}

interface EventoPagamento {

tipo: &#039;pagamento&#039;;

acao: &#039;processado&#039; | &#039;recusado&#039; | &#039;pendente&#039;;

valor: number;

moeda: string;

timestamp: Date;

}

// Type guard reutilizável

function ehEventoUsuario(evento: Evento): evento is EventoUsuario {

return evento.tipo === &#039;usuario&#039;;

}

// Processador que combina tudo

function processarEvento(evento: Evento): void {

// Verificação geral: propriedades comuns a todos

console.log(Evento processado em ${evento.timestamp});

console.log(Ação: ${evento.acao});

// Type narrowing com discriminante

if (ehEventoUsuario(evento)) {

console.log(Usuário ID: ${evento.usuarioId});

} else if (evento.tipo === &#039;produto&#039;) {

console.log(Produto ID: ${evento.produtoId});

console.log(Estoque: ${evento.estoque});

} else if (evento.tipo === &#039;pagamento&#039;) {

console.log(Valor: ${evento.valor} ${evento.moeda});

}

}

// Testando

const eventoUser: Evento = {

tipo: &#039;usuario&#039;,

acao: &#039;criacao&#039;,

usuarioId: 123,

timestamp: new Date()

};

processarEvento(eventoUser);</code></pre>

<h2>Conclusão</h2>

<p>Você aprendeu que <strong>Union Types</strong> permitem flexibilidade ao aceitar múltiplos tipos, mantendo segurança através de verificações explícitas. <strong>Intersection Types</strong> combinam características de múltiplas interfaces, permitindo composição elegante de comportamentos. <strong>Type Guards</strong> reduzem o escopo de tipos através de verificações de runtime, usando técnicas como <code>typeof</code>, <code>instanceof</code>, propriedades discriminantes e type predicates customizados. Quando combinadas, essas três abordagens criam código TypeScript robusto, legível e imune a erros de tipo em tempo de execução.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html" target="_blank" rel="noopener noreferrer">TypeScript Handbook - Union Types</a></li>

<li><a href="https://www.typescriptlang.org/docs/handbook/2/narrowing.html" target="_blank" rel="noopener noreferrer">TypeScript Handbook - Type Guards and Differentiating Types</a></li>

<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof" target="_blank" rel="noopener noreferrer">MDN Web Docs - instanceof operator</a></li>

<li><a href="https://basarat.gitbook.io/typescript/type-system/discriminated-unions" target="_blank" rel="noopener noreferrer">TypeScript Deep Dive - Discriminated Unions</a></li>

<li><a href="https://www.oreilly.com/library/view/programming-typescript/9781492037644/" target="_blank" rel="noopener noreferrer">O&#039;Reilly - Programming TypeScript by Boris Cherny</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em TypeScript

Funções em TypeScript: Assinaturas, Overloads e this Tipado na Prática
Funções em TypeScript: Assinaturas, Overloads e this Tipado na Prática

Fundamentos de Funções em TypeScript Uma função em TypeScript é bem mais que...

Interfaces em TypeScript: Definição, Extensão e Merge Declaration na Prática
Interfaces em TypeScript: Definição, Extensão e Merge Declaration na Prática

O que é uma Interface em TypeScript? Uma interface em TypeScript é um contrat...

Como Usar Constraints e Default Types em Generics TypeScript em Produção
Como Usar Constraints e Default Types em Generics TypeScript em Produção

Entendendo Generics em TypeScript Os generics são um dos recursos mais podero...