Generics em TypeScript: para que servem e como usar
TypeScript é uma ferramenta poderosa que adiciona tipagem estática ao JavaScript. No entanto, se você está começando a aprender TypeScript, pode ter se deparado com situações em que precisa escrever o mesmo código para diferentes tipos de dados. É aí que entram os Generics (tipos genéricos). Neste artigo, vamos explorar o que são Generics, para que servem e como usá-los na prática.
O que são Generics e por que são importantes?
Generics são um mecanismo do TypeScript que permite criar componentes (funções, classes, interfaces) que funcionam com diferentes tipos de dados, em vez de um único tipo específico. Imagine que você está escrevendo uma função que precisa funcionar com números, strings e arrays. Sem os genéricos, você teria que usar o tipo any (o que anula o propósito do TypeScript) ou criar várias cópias da mesma função.
Principais vantagens dos Generics:
- Type-safety — você mantém a tipagem forte, mas o código permanece flexível.
- Reutilização de código — uma função ou classe pode funcionar com diferentes tipos.
- Legibilidade — o código se torna mais claro e autodocumentável.
Sintaxe básica dos Generics
Vamos começar com o exemplo mais simples. Suponha que precisamos de uma função que retorna o argumento recebido. Sem Generics, escreveríamos assim:
function identity(arg: number): number { return arg;}Mas e se precisarmos passar uma string? Teríamos que criar uma nova função. Com Generics, fazemos assim:
function identity<T>(arg: T): T { return arg;}
// Uso:let output1 = identity<string>("Olá");let output2 = identity<number>(42);let output3 = identity<boolean>(true);Aqui, <T> é um parâmetro de tipo (ou variável de tipo). Quando chamamos a função, o TypeScript substitui automaticamente pelo tipo necessário. Você pode usar qualquer letra: T, U, V ou até nomes descritivos como Type.
A propósito, o TypeScript pode inferir o tipo automaticamente, então muitas vezes podemos escrever sem especificar explicitamente:
let output = identity("Olá"); // TypeScript entende que T = stringTrabalhando com arrays e múltiplos tipos
Uma das aplicações mais comuns dos Generics é trabalhar com arrays. Suponha que queremos escrever uma função que recebe um array e retorna o primeiro elemento:
function getFirstElement<T>(arr: T[]): T | undefined { return arr[0];}
const numbers = [1, 2, 3];const firstNumber = getFirstElement(numbers); // tipo number | undefined
const strings = ["a", "b", "c"];const firstString = getFirstElement(strings); // tipo string | undefinedObserve: usamos T[] — que é o mesmo que Array<T>. E se a função receber dois tipos diferentes? Fácil!
function merge<T, U>(obj1: T, obj2: U): T & U { return { ...obj1, ...obj2 };}
const result = merge({ name: "Ana" }, { age: 25 });// result tem o tipo { name: string; } & { age: number; }// Na prática: { name: string; age: number; }Restrições de Generics (Constraints)
Às vezes, precisamos que o genérico funcione apenas com tipos que possuem determinadas propriedades. Para isso, usamos restrições com a palavra-chave extends.
Por exemplo, queremos escrever uma função que obtém o comprimento de um objeto. Obviamente, nem todos os tipos têm a propriedade length. Vamos verificar:
function getLength<T extends { length: number