Introdução: por que sem generics o TypeScript perde metade da força?
Imagine que você é um arquiteto que projeta um prédio do zero toda vez, mesmo quando precisa apenas construir mais uma garagem igual. É mais ou menos assim que se sente um desenvolvedor que escreve a mesma função para números, strings e arrays, sem usar TypeScript generics. Parece familiar? Se você já domina os tipos básicos em TypeScript, mas sente que escreve código duplicado demais, este artigo é para você.
Generics são um mecanismo que permite criar componentes (funções, classes, interfaces) que funcionam com diferentes tipos, em vez de um único específico. Em vez de copiar código, você o escreve uma vez, e o tipo é inserido automaticamente. Neste artigo, vamos explorar como a programação genérica funciona em TypeScript, mostrar exemplos reais de código e dar dicas práticas que tornarão seu código mais limpo e seguro.
Somos a PythonLib, e hoje falamos sobre TypeScript porque o desenvolvimento frontend moderno é impensável sem ele. Vamos nessa!
O que são generics em TypeScript e para que servem?
Vamos começar com um exemplo simples. Suponha que você queira escrever uma função que retorna o argumento recebido (função identidade).
// Sem generics — é necessário usar anyfunction identity(arg: any): any { return arg;}
// Ou escrever sobrecargas para cada tipofunction identityNumber(arg: number): number { return arg; }function identityString(arg: string): string { return arg; }A primeira opção destrói a tipagem — você perde informações sobre o tipo. A segunda obriga a criar várias funções idênticas. Generics TypeScript resolvem esse problema de forma elegante:
function identity<T>(arg: T): T { return arg;}
const num = identity<number>(42); // tipo numberconst str = identity('hello'); // tipo string (inferência de tipo)const bool = identity(true); // tipo booleanO símbolo <T> é o parâmetro de tipo. Você pode nomeá-lo como quiser (frequentemente usam T, U, V), mas é melhor dar nomes significativos se houver muitos. Quando você chama a função, o TypeScript substitui o tipo real no lugar de T. Se o tipo não for especificado explicitamente, o compilador o infere automaticamente a partir do argumento.
Assim, os tipos genéricos (Generics) permitem manter a tipagem estrita onde antes era necessário usar any. Isso reduz a quantidade de bugs e torna o código autodocumentável.
Sintaxe dos generics: do simples ao complexo
Funções genéricas
Já vimos o exemplo básico. Vamos complicar: uma função que recebe um array e retorna seu primeiro elemento.
function getFirst<T>(arr: T[]): T | undefined { return arr[0];}
const firstNumber = getFirst([1, 2, 3]); // number | undefinedconst firstString = getFirst(['a', 'b', 'c']); // string | undefinedObserve: T[] é uma abreviação de Array<T>. Você pode usar qualquer forma.
Interfaces genéricas
Interfaces com generics são uma ferramenta poderosa para descrever estruturas de dados. Por exemplo, uma resposta de API tipada:
interface ApiResponse<T> { data: T; status: number; message: string;}
// Usoconst userResponse: ApiResponse<{ id: number; name: string }> = { data: { id: 1, name: 'Alice' }, status: 200, message: 'OK'};Agora uma única interface pode descrever respostas para usuários, produtos, pedidos — basta inserir o tipo adequado.
Classes genéricas
Classes também podem ser genéricas. Um exemplo clássico é a fila (Queue):
c