3 min de leitura
•13 Jun, 2025Afinal, para que servem os generics?
Você quer criar uma função que compare dois números e lance um erro se forem diferentes:
const validateEqualNumbers = (a: number, b: number) => {
if (a !== b) throw new Error(`Os valores ${a} e ${b} não são iguais`)
}
Depois você precisa fazer o mesmo com strings:
const validateEqualNumbers = (a: number, b: number) => {
if (a !== b) {
throw new Error(`Os valores ${a} e ${b} não são iguais`);
}
};
const validateEqualStrings = (a: string, b: string) => {
if (a !== b) {
throw new Error(`Os valores ${a} e ${b} não são iguais`);
}
};
Depois você precisa fazer o mesmo com strings:
const validateEqual = (a: any, b: any) => {
if (a !== b) {
throw new Error(`Os valores ${a} e ${b} não são iguais`);
}
};
Mas any quebra a segurança de tipos, permitindo coisas como:
validateEqual(42, "hello world");
Comparar número com string não faz sentido.
É aí que Generics se torna necessário: criar funções reutilizáveis que mantêm segurança de tipos, sem especificar tipos antecipadamente.
Utilizando Generics
Para usar um tipo genérico, passe um parâmetro de tipo entre o sinal de maior e menor:
const mustBeEqual = <T>(x: T, y: T) => {
if (x !== y) {
throw new Error(`${x} isn't equal to ${y}`);
}
};
Funções genéricas têm dois tipos de parâmetros: tipos (generics) e variáveis.
O parâmetro de variável é representado pelo conteúdo entre parênteses e o de tipo, é representado pelo conteúdo entre os sinais de maior e menor.
<T> é apenas um nome, pode ser qualquer outro. Você pode usar múltiplos parâmetros separados por vírgula, valores padrão com =, e referências entre eles:
const func = <T, V = number, K = V[]>(foo: T, bar: V, baz: K) => {}
Generics funciona em classes, interfaces e tipos também. Para criar uma interface genérica, passe os parâmetros após o nome:
interface Data<T> {
id: T;
}
Exemplo prático - filtrar array de Data<T> por id:
const filterById = <T>(id: T, data: Data<T>[]) => {
return data.filter((x) => x.id !== id);
};
Utilizando Type Constraints
A função anterior tem um problema: permite id de qualquer tipo, até objeto ou array, mas id deveria ser apenas string ou number.
Para resolver, use extends para criar uma restrição (type constraint) que limita quais tipos são aceitos:
interface Data<T extends string | number> {
id: T;
}
const filterByIdExtended = <T extends string | number>(id: T, data: Data<T>[]) => {
return data.filter((x) => x.id !== id);
};
Agora a função aceita apenas objetos com propriedade id do tipo string ou number.
Ops…
Generics já está presente no seu código. Quando você escreve number[], na verdade está usando Array<number>.
É possível especificar o tipo genérico manualmente:
const getLast = <T>(items: T[]): T => items[items.length - 1]
const numbers = [10, 20, 30]
getLast(numbers) // Tipo inferido automaticamente
getLast<number>(numbers) // Tipo especificado manualmente
getLast<string>(numbers) // Erro: tipos incompatíveis
TypeScript infere tipos automaticamente, mas quando você especifica manualmente, ele valida a compatibilidade rigorosamente.