Pular para o conteúdo
TypeScript

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:

  1. Passo 1: Identifique código duplicado que só muda o tipo. Se você tem 'processString', 'processNumber' fazendo a mesma coisa, é hora de um generic.
  2. Passo 2: Adicione o parâmetro de tipo entre <T> depois do nome da função. Agora T é um 'placeholder' pro tipo real.
  3. Passo 3: Use T nos parâmetros e no retorno. O TypeScript infere T automaticamente quando você chama a função.
  4. Passo 4: Precisa restringir T? Use T extends SomeType. Isso garante que T tem propriedades específicas.
  5. Passo 5: Quer valor padrão pro generic? Use T = DefaultType. Quem chamar sem especificar T recebe o default.
  6. Passo 6: Múltiplos generics? Separe com vírgula: <T, U>. Cada um é independente.
  7. Passo 7: Teste chamando a função com tipos diferentes. Se o autocomplete mostra o tipo correto, tá funcionando.

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 .

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 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([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

  • Identifiquei código duplicado que só varia no tipo.
  • Criei generic com e usei constraint quando T precisa de propriedades específicas.
  • Aproveitei inferência de tipo ao invés de especificar T manualmente.
  • Não criei generic desnecessário pra funções com tipo fixo.
  • Generic interfaces têm valores padrão quando faz sentido.
  • Testei o generic com tipos diferentes pra confirmar que funciona.
  • Autocomplete mostra o tipo correto no retorno das funções genéricas.
  • Documentei generics complexos com comentários curtos.

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.

Perguntas frequentes

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.

Como Usar Generics Passo a Passo

Segue esse roteiro e generics param de parecer mágica: