JavaScript

Boas Práticas de Tipos Avançados em TypeScript: Union, Intersection, Generics e Utility Types para Times Ágeis

8 min de leitura

Boas Práticas de Tipos Avançados em TypeScript: Union, Intersection, Generics e Utility Types para Times Ágeis

Union Types: Combinando Múltiplos Tipos Union types permitem que uma variável aceite múltiplos tipos específicos. É um dos mecanismos mais práticos para criar APIs flexíveis sem perder segurança de tipos. Use a sintaxe de barra vertical (|) para declarar um union type. A vantagem emerge ao usar type guards para estreitar o tipo em tempo de execução. O TypeScript então oferece autocompletar específico para cada tipo. Padrões como , e discriminated unions (usando propriedades literais) garantem código robusto e legível. Intersection Types: Combinando Propriedades Intersection types (& operador) mesclam múltiplos tipos em um único tipo que possui todas as propriedades de ambos. É diferente de union: aqui o valor deve satisfazer todos os tipos simultaneamente, não apenas um. Intersection types são especialmente úteis para composição de tipos e para adicionar capacidades específicas a tipos existentes. Use-os quando precisar garantir que um objeto cumpra múltiplos contratos simultaneamente. Evite uso excessivo, pois tornam assinaturas complexas; prefira composição com interfaces quando apropriado. Generics:

<h2>Union Types: Combinando Múltiplos Tipos</h2>

<p>Union types permitem que uma variável aceite múltiplos tipos específicos. É um dos mecanismos mais práticos para criar APIs flexíveis sem perder segurança de tipos. Use a sintaxe de barra vertical ( | ) para declarar um union type.</p> <pre><code class="language-typescript">type Status = &quot;pending&quot; | &quot;approved&quot; | &quot;rejected&quot;; type Result = string | number;

function processPayment(status: Status): void {

if (status === &quot;pending&quot;) {

console.log(&quot;Processando pagamento...&quot;);

}

}

const userId: Result = 42; // válido

const userName: Result = &quot;João&quot;; // válido

// const invalid: Result = true; // erro</code></pre>

<p>A vantagem emerge ao usar <strong>type guards</strong> para estreitar o tipo em tempo de execução. O TypeScript então oferece autocompletar específico para cada tipo. Padrões como <code>typeof</code>, <code>instanceof</code> e discriminated unions (usando propriedades literais) garantem código robusto e legível.</p>

<pre><code class="language-typescript">type Response = { success: true; data: string } | { success: false; error: string };

function handleResponse(res: Response): void {

if (res.success) {

console.log(res.data); // tipo: string

} else {

console.log(res.error); // tipo: string

}

}</code></pre>

<h2>Intersection Types: Combinando Propriedades</h2>

<p>Intersection types (&amp; operador) mesclam múltiplos tipos em um único tipo que possui todas as propriedades de ambos. É diferente de union: aqui o valor deve satisfazer <strong>todos</strong> os tipos simultaneamente, não apenas um.</p>

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

nome: string;

salario: number;

}

interface Gerente {

departamento: string;

equipe: number;

}

type GerenteFuncionario = Funcionario &amp; Gerente;

const gerente: GerenteFuncionario = {

nome: &quot;Maria&quot;,

salario: 5000,

departamento: &quot;TI&quot;,

equipe: 5

};</code></pre>

<p>Intersection types são especialmente úteis para composição de tipos e para adicionar capacidades específicas a tipos existentes. Use-os quando precisar garantir que um objeto cumpra múltiplos contratos simultaneamente. Evite uso excessivo, pois tornam assinaturas complexas; prefira composição com interfaces quando apropriado.</p>

<pre><code class="language-typescript">type Auditavel = { criado: Date; atualizado: Date };

type Validavel = { validar(): boolean };

type Documento = { titulo: string } &amp; Auditavel &amp; Validavel;

const doc: Documento = {

titulo: &quot;Contrato&quot;,

criado: new Date(),

atualizado: new Date(),

validar() { return true; }

};</code></pre>

<h2>Generics: Reutilização Parametrizada de Tipos</h2>

<p>Generics são variáveis de tipo que tornam componentes reutilizáveis enquanto mantêm segurança estática. São fundamentais em TypeScript profissional. Declare um generic com &lt;T&gt; entre chevrons, onde T é convenção para &quot;Type&quot;.</p>

<pre><code class="language-typescript">function obterPrimeiro&lt;T&gt;(lista: T[]): T {

return lista[0];

}

const numeros = obterPrimeiro([1, 2, 3]); // tipo: number

const nomes = obterPrimeiro([&quot;Ana&quot;, &quot;Bruno&quot;]); // tipo: string

// Generics com constraints

function obterPropriedade&lt;T, K extends keyof T&gt;(obj: T, chave: K): T[K] {

return obj[chave];

}

const pessoa = { nome: &quot;João&quot;, idade: 30 };

const idade = obterPropriedade(pessoa, &quot;idade&quot;); // tipo: number

// obterPropriedade(pessoa, &quot;email&quot;); // erro: propriedade inexistente</code></pre>

<p>Generics com constraints (<code>extends</code>) limitam quais tipos podem ser passados. Use <code>keyof T</code> para tipos seguros de propriedades. Para casos avançados, combine com unions e intersections. Generics em classes modelam estruturas genéricas como pilhas, filas e repositórios sem duplicação de código.</p>

<pre><code class="language-typescript">class Repositorio&lt;T&gt; {

private dados: T[] = [];

adicionar(item: T): void {

this.dados.push(item);

}

obter(indice: number): T | undefined {

return this.dados[indice];

}

listar(): T[] {

return [...this.dados];

}

}

interface Usuario {

id: number;

email: string;

}

const repoUsuarios = new Repositorio&lt;Usuario&gt;();

repoUsuarios.adicionar({ id: 1, email: &quot;user@example.com&quot; });

const usuario = repoUsuarios.obter(0); // tipo: Usuario | undefined</code></pre>

<h2>Utility Types: Transformações Prontas</h2>

<p>TypeScript fornece utility types nativos que transformam tipos existentes de forma poderosa. São verdadeiros multiplicadores de produtividade.</p>

<h3>Principais Utility Types</h3>

<p><strong>Partial&lt;T&gt;</strong> torna todas as propriedades opcionais; <strong>Required&lt;T&gt;</strong> faz o oposto. <strong>Pick&lt;T, Keys&gt;</strong> seleciona propriedades específicas; <strong>Omit&lt;T, Keys&gt;</strong> remove propriedades. <strong>Record&lt;K, V&gt;</strong> cria objetos com chaves conhecidas. <strong>Readonly&lt;T&gt;</strong> torna propriedades imutáveis.</p>

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

id: number;

nome: string;

preco: number;

descricao: string;

}

// Partial: todas propriedades opcionais para atualização

type AtualizarProduto = Partial&lt;Produto&gt;;

// Pick: apenas nome e preco para exibição

type ProdutoResumido = Pick&lt;Produto, &quot;nome&quot; | &quot;preco&quot;&gt;;

// Omit: tudo exceto id (para criação)

type CriarProduto = Omit&lt;Produto, &quot;id&quot;&gt;;

// Record: mapa de categorias

type CategoriaInventario = Record&lt;&quot;eletronicos&quot; | &quot;livros&quot; | &quot;roupas&quot;, number&gt;;

const estoque: CategoriaInventario = {

eletronicos: 50,

livros: 120,

roupas: 200

};

// Readonly: preço não pode ser modificado

type ProdutoImutavel = Readonly&lt;Produto&gt;;</code></pre>

<p><strong>Extract&lt;T, U&gt;</strong> extrai tipos que correspondem a U de T; <strong>Exclude&lt;T, U&gt;</strong> faz o oposto. <strong>ReturnType&lt;F&gt;</strong> obtém o tipo de retorno de uma função. <strong>Parameters&lt;F&gt;</strong> extrai os tipos de parâmetros. Estes são indispensáveis em código metaprogramado e em bibliotecas robustas.</p>

<pre><code class="language-typescript">// Extract e Exclude

type Status = &quot;ativo&quot; | &quot;inativo&quot; | &quot;pendente&quot; | &quot;cancelado&quot;; type StatusAtivos = Exclude&lt;Status, &quot;cancelado&quot;&gt;; // &quot;ativo&quot; | &quot;inativo&quot; | &quot;pendente&quot;

type ApenasAtivo = Extract&lt;Status, &quot;ativo&quot;&gt;; // &quot;ativo&quot;

// ReturnType e Parameters

function autenticar(usuario: string, senha: string): { token: string } {

return { token: &quot;xyz&quot; };

}

type RetornoAuth = ReturnType&lt;typeof autenticar&gt;; // { token: string }

type ParamsAuth = Parameters&lt;typeof autenticar&gt;; // [string, string]</code></pre>

<h2>Conclusão</h2>

<p>Três aprendizados principais consolidam domínio sobre tipos avançados em TypeScript. Primeiro, <strong>unions e intersections</strong> são ferramentas complementares: use unions para alternativas e intersections para composição. Segundo, <strong>generics</strong> são essenciais para código reutilizável que mantém segurança de tipos; não tenha medo de combiná-los com constraints. Terceiro, <strong>utility types</strong> são multiplicadores de produtividade que evitam duplicação; domine os comuns (Partial, Pick, Omit, Record) e explore avançados conforme necessário. Aplicar estes conceitos transforma código em sistemas mais seguros, inteligíveis e mantíveis.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://www.typescriptlang.org/docs/handbook/2/types-from-types.html" target="_blank" rel="noopener noreferrer">TypeScript Handbook - Advanced Types</a></li>

<li><a href="https://www.typescriptlang.org/docs/handbook/utility-types.html" target="_blank" rel="noopener noreferrer">TypeScript Handbook - Utility Types</a></li>

<li><a href="https://effectivetypescript.com/" target="_blank" rel="noopener noreferrer">Effective TypeScript - Dan Vanderkam</a> (Livro recomendado)</li>

<li><a href="https://egghead.io/" target="_blank" rel="noopener noreferrer">Type-Driven Development with TypeScript - Egghead</a></li>

<li><a href="https://basarat.gitbook.io/typescript/" target="_blank" rel="noopener noreferrer">TypeScript Deep Dive - Basarat Ali Syed</a></li>

</ul>

Comentários

Mais em JavaScript

Babel em JavaScript: Transpilação e Compatibilidade entre Ambientes na Prática
Babel em JavaScript: Transpilação e Compatibilidade entre Ambientes na Prática

O que é Babel e por que você precisa dele Babel é um transpilador JavaScript...

Closures em JavaScript: Escopo Léxico e Funções de Primeira Classe na Prática
Closures em JavaScript: Escopo Léxico e Funções de Primeira Classe na Prática

Escopo Léxico: O Alicerce das Closures O escopo léxico é a regra fundamental...

Boas Práticas de Proxy e Reflect em JavaScript: Interceptando Operações em Objetos para Times Ágeis
Boas Práticas de Proxy e Reflect em JavaScript: Interceptando Operações em Objetos para Times Ágeis

Introdução: Por que Proxy e Reflect Importam para Times Ágeis Proxy e Reflect...