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.
type Status = "pending" | "approved" | "rejected";
type Result = string | number;
function processPayment(status: Status): void {
if (status === "pending") {
console.log("Processando pagamento...");
}
}
const userId: Result = 42; // válido
const userName: Result = "João"; // válido
// const invalid: Result = true; // erro
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 typeof, instanceof e discriminated unions (usando propriedades literais) garantem código robusto e legível.
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
}
}
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.
interface Funcionario {
nome: string;
salario: number;
}
interface Gerente {
departamento: string;
equipe: number;
}
type GerenteFuncionario = Funcionario & Gerente;
const gerente: GerenteFuncionario = {
nome: "Maria",
salario: 5000,
departamento: "TI",
equipe: 5
};
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.
type Auditavel = { criado: Date; atualizado: Date };
type Validavel = { validar(): boolean };
type Documento = { titulo: string } & Auditavel & Validavel;
const doc: Documento = {
titulo: "Contrato",
criado: new Date(),
atualizado: new Date(),
validar() { return true; }
};
Generics: Reutilização Parametrizada de Tipos
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
function obterPrimeiro<T>(lista: T[]): T {
return lista[0];
}
const numeros = obterPrimeiro([1, 2, 3]); // tipo: number
const nomes = obterPrimeiro(["Ana", "Bruno"]); // tipo: string
// Generics com constraints
function obterPropriedade<T, K extends keyof T>(obj: T, chave: K): T[K] {
return obj[chave];
}
const pessoa = { nome: "João", idade: 30 };
const idade = obterPropriedade(pessoa, "idade"); // tipo: number
// obterPropriedade(pessoa, "email"); // erro: propriedade inexistente
Generics com constraints (extends) limitam quais tipos podem ser passados. Use keyof T 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.
class Repositorio<T> {
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<Usuario>();
repoUsuarios.adicionar({ id: 1, email: "user@example.com" });
const usuario = repoUsuarios.obter(0); // tipo: Usuario | undefined
Utility Types: Transformações Prontas
TypeScript fornece utility types nativos que transformam tipos existentes de forma poderosa. São verdadeiros multiplicadores de produtividade.
Principais Utility Types
Partial
interface Produto {
id: number;
nome: string;
preco: number;
descricao: string;
}
// Partial: todas propriedades opcionais para atualização
type AtualizarProduto = Partial<Produto>;
// Pick: apenas nome e preco para exibição
type ProdutoResumido = Pick<Produto, "nome" | "preco">;
// Omit: tudo exceto id (para criação)
type CriarProduto = Omit<Produto, "id">;
// Record: mapa de categorias
type CategoriaInventario = Record<"eletronicos" | "livros" | "roupas", number>;
const estoque: CategoriaInventario = {
eletronicos: 50,
livros: 120,
roupas: 200
};
// Readonly: preço não pode ser modificado
type ProdutoImutavel = Readonly<Produto>;
Extract
// Extract e Exclude
type Status = "ativo" | "inativo" | "pendente" | "cancelado";
type StatusAtivos = Exclude<Status, "cancelado">; // "ativo" | "inativo" | "pendente"
type ApenasAtivo = Extract<Status, "ativo">; // "ativo"
// ReturnType e Parameters
function autenticar(usuario: string, senha: string): { token: string } {
return { token: "xyz" };
}
type RetornoAuth = ReturnType<typeof autenticar>; // { token: string }
type ParamsAuth = Parameters<typeof autenticar>; // [string, string]
Conclusão
Três aprendizados principais consolidam domínio sobre tipos avançados em TypeScript. Primeiro, unions e intersections são ferramentas complementares: use unions para alternativas e intersections para composição. Segundo, generics 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, utility types 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.