Generics no TypeScript na Prática
Generics no TypeScript: funções e componentes reutilizáveis sem perder type safety.
Por que isso é importante
Generics no TypeScript deixam função e componente reutilizáveis sem cair em any. Nomeie o parâmetro de tipo pelo papel, não por letra solta sem contexto.
O Que São Generics no TypeScript
Generics são variáveis de tipo. Simples assim. Da mesma forma que uma função recebe um parâmetro de valor, um generic recebe um parâmetro de tipo. Você escreve a lógica uma vez e ela funciona com qualquer tipo que passar.
Imagina uma função que retorna o primeiro elemento de um array. Sem generics, ou você faz uma função pra cada tipo (getFirstString, getFirstNumber) ou usa 'any' e perde toda a tipagem. Com generics, uma única função serve pra tudo e o TypeScript sabe exatamente o tipo de retorno.
O 'T' que você vê em generics é só uma convenção. Pode ser qualquer nome. T pra Type, K pra Key, V pra Value, E pra Element. Mas T é o mais usado e todo dev reconhece.
Dá pra usar generics em funções, interfaces, classes, type aliases e até em componentes React. É a ferramenta mais poderosa do sistema de tipos do TypeScript. Vamos ver cada uso na prática.
Como Usar Generics Passo a Passo
Segue esse roteiro e generics param de parecer mágica:
<T> depois do nome da função. Agora T é um 'placeholder' pro tipo real.T extends SomeType. Isso garante que T tem propriedades específicas.T = DefaultType. Quem chamar sem especificar T recebe o default.<T, U>. Cada um é independente.Exemplos Práticos
Começando pela generic function mais básica:
// Sem generics - repetição ou any
function firstString(arr: string[]): string { return arr[0]; }
function firstNumber(arr: number[]): number { return arr[0]; }
function firstAny(arr: any[]): any { return arr[0]; } // perde tipo
// Com generics - uma função serve pra tudo
function first<T>(arr: T[]): T {
return arr[0];
}
// TypeScript infere T automaticamente
const name = first(["Ana", "Carlos"]); // tipo: string
const age = first([25, 30, 22]); // tipo: number
const flag = first([true, false]); // tipo: boolean
// Ou você pode explicitar
const id = first<number>([1, 2, 3]); // tipo: number
Generics com múltiplos parâmetros e objetos:
// Dois parâmetros genéricos
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const result = pair("idade", 25); // tipo: [string, number]
// Generic interface
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
interface User { id: number; name: string; }
interface Product { id: number; price: number; }
// Mesma estrutura, dados diferentes
const userRes: ApiResponse<User> = {
data: { id: 1, name: "Ana" },
status: 200,
message: "OK",
};
const productRes: ApiResponse<Product> = {
data: { id: 1, price: 99.90 },
status: 200,
message: "OK",
};
Constraints com extends. Aqui generics ficam realmente poderosos:
// T precisa ter a propriedade 'length'
function logLength<T extends { length: number }>(item: T): void {
console.log(item.length);
}
logLength("texto"); // OK, string tem length
logLength([1, 2, 3]); // OK, array tem length
// logLength(42); // ERRO! number não tem length
// T precisa ter 'id'
function findById<T extends { id: number }>(items: T[], id: number): T | undefined {
return items.find(item => item.id === id);
}
const users = [{ id: 1, name: "Ana" }, { id: 2, name: "Bob" }];
const found = findById(users, 1); // tipo: { id: number; name: string } | undefined
// keyof constraint - T é chave de U
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Ana", age: 28 };
const name = getProperty(user, "name"); // tipo: string
const age = getProperty(user, "age"); // tipo: number
// getProperty(user, "email"); // ERRO! "email" não é chave de user
Patterns comuns que aparecem em projetos reais:
// Generic com valor padrão
interface PaginatedResponse<T = unknown> {
items: T[];
total: number;
page: number;
perPage: number;
}
// Pode usar sem especificar T
const res1: PaginatedResponse = { items: [], total: 0, page: 1, perPage: 10 };
// Ou com tipo específico
const res2: PaginatedResponse<User> = {
items: [{ id: 1, name: "Ana" }],
total: 1, page: 1, perPage: 10,
};
// Factory pattern com generics
function createStore<T>(initialValue: T) {
let value = initialValue;
return {
get: (): T => value,
set: (newValue: T): void => { value = newValue; },
};
}
const nameStore = createStore("Ana");
nameStore.set("Carlos"); // OK
// nameStore.set(42); // ERRO! esperava string
const counterStore = createStore(0);
counterStore.set(10); // OK
// counterStore.set("dez"); // ERRO! esperava number
Erros Comuns
Armadilhas clássicas de generics
Erro 1: Usar generic quando não precisa. Se a função só trabalha com um tipo, não force um generic. 'function add(a: number, b: number)' não precisa de <T>.
Erro 2: Esquecer o constraint e tratar T como tipo específico. Se você faz 'item.length' dentro de uma função genérica, precisa de 'T extends { length: number }'. Sem constraint, T pode ser qualquer coisa.
Erro 3: Generic demais. Funções com <T, U, V, W, X> são ilegíveis. Se tem mais de 3 parâmetros genéricos, provavelmente dá pra simplificar.
Erro 4: Não aproveitar inferência. Na maioria dos casos, não precisa especificar T na chamada. 'first([1,2,3])' já infere T = number. Escrever 'first<number>([1,2,3])' é redundante.
Erro 5: Confundir generic com union. 'T extends string | number' é diferente de 'param: string | number'. O generic mantém o tipo específico no retorno.
Checklist: Generics
Generics na prática, em projeto real
Generics são a base de código limpo e reutilizável em TypeScript. No CrazyStack, você constrói services genéricos, repositories tipados, hooks customizados e componentes React flexíveis. Tudo usando generics do jeito certo.
Não é teoria solta. Você implementa um SaaS completo com Node.js e React, tudo tipado. O projeto fica pronto pra deploy e pro seu portfólio. É o tipo de experiência que recrutadores procuram.
Continue lendo
Diferença entre Type e Interface no TypeScript
Entenda quando usar type e quando usar interface no TypeScript.
Como Tipar Funções no TypeScript
A tipar funções no TypeScript: parâmetros, retorno e callbacks.
Como Tipar Array no TypeScript
Todas as formas de tipar arrays no TypeScript com exemplos.
Interface no TypeScript
Quando usar interface