Se vocĂȘ estĂĄ aprendendo TypeScript, jĂĄ deve ter ouvido falar sobre âGenericsâ. O nome pode parecer complicado, mas a ideia por trĂĄs Ă© muito poderosa e, na verdade, bastante intuitiva. Generics sĂŁo uma das ferramentas mais legais do TypeScript para escrever cĂłdigo flexĂvel, seguro e reutilizĂĄvel.
Neste guia, vamos desvendar o que sĂŁo Generics passo a passo, com exemplos prĂĄticos e analogias para que vocĂȘ perca o medo e comece a usĂĄ-los hoje mesmo.
O Problema: CĂłdigo Repetitivo e Inseguro
Imagine que vocĂȘ precisa criar uma função simples que recebe um array e retorna o primeiro elemento.
Se o array for de nĂșmeros, vocĂȘ faria algo assim:
function primeiroElementoNumero(array: number[]): number | undefined {
return array[0];
}
E se o array for de textos (strings)? VocĂȘ teria que criar outra função:
function primeiroElementoString(array: string[]): string | undefined {
return array[0];
}
Veja o problema? Estamos repetindo o mesmo código só para mudar o tipo. Uma primeira solução seria usar o tipo any, que aceita qualquer coisa:
// A solução com 'any' (mas é uma må ideia!)
function primeiroElementoQualquer(array: any[]): any {
return array[0];
}
const primeiroNome = primeiroElementoQualquer(["Ana", "JoĂŁo"]); // OK
primeiroNome.toUpperCase();
// O TypeScript nĂŁo sabe que isso Ă© uma string! Ele nĂŁo ajuda.
Ao usar any, perdemos toda a segurança e o autocompletar que o TypeScript nos oferece. à aqui que os Generics entram para salvar o dia!
A Solução: Criando uma âFunção-Moldeâ com Generics
Generics permitem criar um âmoldeâ de função, classe ou interface. Em vez de definir um tipo fixo (como number ou string), vocĂȘ usa um âparĂąmetro de tipoâ, que funciona como uma variĂĄvel para o tipo. A convenção Ă© usar a letra T (de âTypeâ).
Vamos reescrever nossa função primeiroElemento usando Generics:
// Nossa primeira função genérica!
function primeiroElemento<T>(array: T[]): T | undefined {
return array[0];
}
Vamos quebrar o que aconteceu aqui:
<T>: Aqui, estamos dizendo: âEsta função vai usar um tipo genĂ©rico que eu vou chamar deTâ.array: T[]: O argumentoarrayserĂĄ um array deâŠT! Seja qual for o tipo queTse torne.: T | undefined: A função retornarĂĄ um valor do mesmo tipoT(ouundefinedse o array estiver vazio).
Agora, veja a mĂĄgica acontecer:
const numeros = [1, 2, 3, 4, 5];
const primeiroNum = primeiroElemento(numeros);
// TypeScript sabe que 'primeiroNum' Ă© do tipo number
const nomes = ["Ana", "JoĂŁo", "Maria"];
const primeiroNome = primeiroElemento(nomes);
// TypeScript sabe que 'primeiroNome' Ă© do tipo string
// Agora o autocompletar funciona!
console.log(primeiroNome.toUpperCase());
// "ANA" - O editor sugere métodos de string!
Com uma Ășnica função, resolvemos o problema para todos os tipos de array, mantendo nosso cĂłdigo seguro e inteligente!
Criando âCaixasâ FlexĂveis: Interfaces GenĂ©ricas
VocĂȘ tambĂ©m pode usar Generics com interfaces para criar âmoldesâ de objetos. Isso Ă© super comum ao trabalhar com respostas de APIs.
Imagine que uma API sempre retorna um objeto com um campo dados, mas o conteĂșdo desses dados pode variar.
// Uma interface genérica para respostas de API
interface RespostaAPI<TipoDosDados> {
dados: TipoDosDados;
sucesso: boolean;
timestamp: number;
}
// Exemplo 1: Resposta com dados de um usuĂĄrio
interface Usuario {
id: number;
nome: string;
}
const respostaUsuario: RespostaAPI<Usuario> = {
dados: { id: 1, nome: "JoĂŁo" },
sucesso: true,
timestamp: 1672531200
};
// Exemplo 2: Resposta com uma lista de produtos
const respostaProdutos: RespostaAPI<string[]> = {
dados: ["Notebook", "Mouse", "Teclado"],
sucesso: true,
timestamp: 1672531201
};
Usamos TipoDosDados para ser mais descritivo que T. VocĂȘ pode nomear seus parĂąmetros de tipo como quiser!
Criando âEstruturasâ ReutilizĂĄveis: Classes GenĂ©ricas
Generics tambĂ©m brilham em classes. Vamos criar uma classe Pilha (Stack), uma estrutura de dados onde o Ășltimo item a entrar Ă© o primeiro a sair (como uma pilha de pratos).
class Pilha<TipoDoItem> {
private items: TipoDoItem[] = [];
// Adiciona um item no topo da pilha
push(item: TipoDoItem): void {
this.items.push(item);
}
// Remove e retorna o item do topo da pilha
pop(): TipoDoItem | undefined {
return this.items.pop();
}
}
// Criando uma pilha de nĂșmeros
const pilhaDeNumeros = new Pilha<number>();
pilhaDeNumeros.push(10);
pilhaDeNumeros.push(20);
console.log(pilhaDeNumeros.pop()); // SaĂda: 20
// Criando uma pilha de textos
const pilhaDeTextos = new Pilha<string>();
pilhaDeTextos.push("OlĂĄ");
pilhaDeTextos.push("Mundo");
console.log(pilhaDeTextos.pop()); // SaĂda: "Mundo"
A mesma classe Pilha funciona perfeitamente para qualquer tipo de dado, evitando duplicação e mantendo a segurança de tipos.
Colocando âRegrasâ: Constraints (RestriçÔes)
Ăs vezes, vocĂȘ nĂŁo quer que sua função genĂ©rica aceite qualquer tipo. VocĂȘ pode querer que ela aceite apenas tipos que sigam certas âregrasâ. Isso Ă© chamado de constraint (restrição).
Imagine uma função que busca um item em um array pelo seu id. A função só faz sentido se os itens do array tiverem uma propriedade id.
// 1. Primeiro, definimos a "regra": o objeto DEVE ter um 'id' do tipo number
interface TemId {
id: number;
}
// 2. Usamos 'extends' para aplicar a regra ao nosso Generic
function buscarPorId<T extends TemId>(items: T[], id: number): T | undefined {
return items.find(item => item.id === id);
}
// Exemplo de uso:
const produtos = [
{ id: 1, nome: "Notebook", preco: 2000 },
{ id: 2, nome: "Mouse", preco: 50 }
];
const produtoEncontrado = buscarPorId(produtos, 1);// Funciona! Os produtos tĂȘm 'id'
const configuracoes = [
{ nome: "dark_mode", valor: true }
];
//const configEncontrada = buscarPorId(configuracoes,0);//ERRO! Os itens sem 'id'
A palavra-chave extends garante que o tipo T seja, no mĂnimo, compatĂvel com a interface TemId.
Por que vocĂȘ vai amar usar Generics?
- Escreva menos cĂłdigo: Crie componentes que funcionam para mĂșltiplos tipos, evitando o famoso âcopia e colaâ.
- Escreva cĂłdigo mais seguro: O TypeScript verifica os tipos para vocĂȘ, pegando erros antes mesmo de vocĂȘ rodar o cĂłdigo. Chega de
cannot read property 'toUpperCase' of undefined! - Código mais inteligente: Seu editor (como o VS Code) vai te dar sugestÔes e autocompletar muito mais precisos, porque ele sabe exatamente qual tipo estå sendo usado.
- Flexibilidade: VocĂȘ cria âferramentasâ (funçÔes, classes) que podem ser adaptadas para diferentes necessidades sem perder a robustez.
ConclusĂŁo
Generics podem parecer um conceito avançado, mas, como vimos, eles resolvem um problema muito comum: como escrever cĂłdigo que seja ao mesmo tempo flexĂvel e seguro.
Não se preocupe se tudo não fez sentido de primeira. A melhor forma de aprender é praticando. Tente criar sua própria função genérica ou uma interface genérica para um projeto seu.
O mais importante Ă© entender a ideia principal: Generics sĂŁo como âvariĂĄveis para tiposâ, permitindo que vocĂȘ crie blocos de cĂłdigo que sĂŁo verdadeiros canivetes suĂços: Ășteis para muitas situaçÔes e sempre muito seguros.