Introdução: Por que você precisa de Generics em JavaScript?
Todo desenvolvedor JavaScript, mais cedo ou mais tarde, enfrenta uma situação onde uma função ou classe precisa trabalhar com diferentes tipos de dados. Sem ferramentas especiais, você acaba duplicando código ou usando any, o que nos tira o controle de tipos. Generics resolvem esse problema.
No contexto do JavaScript, generics são especialmente relevantes ao trabalhar com TypeScript ou ao usar padrões modernos em Node.js. Mesmo que você escreva em JavaScript puro, entender o conceito de Generics ajudará você a escrever um código mais flexível e seguro.
Neste artigo, abordaremos:
- O que são Generics e por que são necessários
- Como usar generics em funções e classes
- Exemplos práticos para Node.js
- Erros comuns de iniciantes
O que são Generics em JavaScript?
Generics são uma forma de criar componentes (funções, classes, interfaces) que podem trabalhar com diferentes tipos de dados, mantendo a segurança de tipos. Em vez de escrever uma função separada para números, outra para strings e outra para objetos, você escreve uma única função universal.
Imagine que você está criando uma caixa. Uma caixa comum pode armazenar apenas um tipo de item. Generics são uma "caixa inteligente" que se adapta ao que você coloca dentro, mas lembra exatamente o que está lá.
Em TypeScript (uma extensão do JavaScript), generics são indicados por colchetes angulares <> e pela letra T (de Type) ou outros identificadores:
// Exemplo mais simples de generic em TypeScript
function identity<T>(arg: T): T {
return arg;
}
// Agora a função funciona com qualquer tipo
const num = identity<number>(42); // tipo number
const str = identity<string>("Hello"); // tipo string
Em JavaScript puro, essa sintaxe não está disponível, mas o conceito continua útil para entender a arquitetura de aplicações.
Por que você precisa de Generics em JavaScript e Node.js?
Vamos considerar três razões principais para entender generics.
1. Segurança de tipos sem perder flexibilidade
Sem generics, você frequentemente usa any, o que desativa a verificação de tipos:
// Ruim: perdemos a informação do tipo
function getFirstElement(arr: any[]): any {
return arr[0];
}
const val = getFirstElement([1, 2, 3]);
// val tem tipo any, não sabemos se é número ou string
Com generics, o tipo é preservado:
// Bom: o tipo é preservado
function getFirstElement<T>(arr: T[]): T {
return arr[0];
}
const val = getFirstElement([1, 2, 3]); // val: number
const str = getFirstElement(["a", "b"]); // str: string
2. Redução da duplicação de código
Sem generics, você teria que escrever funções separadas para cada tipo:
// Sem generics — duplicação
function processNumber(num: number): number { /* ... */ }
function processString(str: string): string { /* ... */ }
function processObject(obj: object): object { /* ... */ }
// Com generics — uma função
function process<T>(data: T): T { /* ... */ }
3. Criação de componentes reutilizáveis
Em Node.js, generics são frequentemente usados para criar utilitários universais, manipuladores de dados e clientes de API:
// Cliente HTTP universal com generics
async function fetchData<T>(url: string): Promise<T> {
const response = await fetch(url);
return response.json() as Promise<T>;
}
// Uso
interface User {
id: number;
name: string;
}
const user = await fetchData<User>('/api/user/1');
// user automaticamente tem o tipo User