Generics do TypeScript para o dia a dia: guia prático

Frontend2026-09-21TryQuickToolBox

Você está escrevendo uma função que funciona com qualquer tipo, mas não quer perder a segurança de tipos. Você poderia usar any, mas isso anula o propósito do TypeScript. Generics permitem escrever código que funciona com vários tipos preservando as informações de tipo. Este guia mostra como usar generics no código do dia a dia sem complicar as coisas.

O que são Generics?

Generics são uma forma de criar componentes reutilizáveis que funcionam com uma variedade de tipos em vez de um único. Eles permitem capturar o tipo da entrada e usá-lo na saída, garantindo segurança de tipos sem sacrificar a flexibilidade.

Pense em generics como parâmetros de tipo. Assim como uma função recebe parâmetros de valor, uma função genérica recebe parâmetros de tipo. Por exemplo, a função identidade:

function identity<T>(arg: T): T {
  return arg;
}

Aqui, T é um parâmetro de tipo. Quando você chama identity<string>("hello"), o TypeScript infere que T é string, e o tipo de retorno é string. Se você passar um número, ele retorna um número. Isso é mais preciso do que usar any.

Por que não usar apenas any?

Usar any desativa a verificação de tipos. Você perde o autocompletar, o suporte à refatoração e o compilador não consegue detectar erros. Generics mantêm as informações de tipo fluindo pelo seu código, então você tem flexibilidade e segurança ao mesmo tempo.

Funções Genéricas na Prática

Vejamos um cenário comum: uma função que retorna o primeiro elemento de um array.

function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

Sem generics, você poderia escrever function firstElement(arr: any[]): any, o que perde o tipo. Com generics, se você passar um string[], o tipo de retorno é string | undefined.

Você também pode usar múltiplos parâmetros de tipo. Por exemplo, uma função que mapeia um array para um novo array usando um callback:

function map<T, U>(arr: T[], fn: (item: T) => U): U[] {
  return arr.map(fn);
}

Isso é essencialmente como Array.prototype.map é tipado na biblioteca padrão do TypeScript.

Interfaces Genéricas e Aliases de Tipo

Generics não se limitam a funções. Você pode criar interfaces genéricas e aliases de tipo para definir estruturas de dados reutilizáveis.

interface Box<T> {
  value: T;
}

const stringBox: Box<string> = { value: "hello" };
const numberBox: Box<number> = { value: 42 };

Isso é útil para respostas de API, gerenciamento de estado ou qualquer tipo de contêiner.

Você também pode fornecer tipos padrão:

interface ApiResponse<T = unknown> {
  data: T;
  status: number;
}

Agora ApiResponse sem um argumento de tipo usa unknown como padrão.

Restrições Genéricas

Às vezes você quer restringir os tipos que podem ser usados com um generic. Você pode usar a palavra-chave extends para adicionar uma restrição.

function getLength<T extends { length: number }>(arg: T): number {
  return arg.length;
}

Agora T deve ter uma propriedade length. Você pode chamar getLength("hello") ou getLength([1,2,3]), mas não getLength(42).

Restrições são poderosas ao trabalhar com objetos que compartilham uma forma comum.

Usando Generics com Classes

Classes também podem ser genéricas. Um exemplo comum é uma coleção genérica:

class Stack<T> {
  private items: T[] = [];

  push(item: T): void {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }
}

const stack = new Stack<number>();
stack.push(1);
stack.push(2);
const popped = stack.pop(); // number | undefined

Isso garante que apenas números possam ser adicionados a uma pilha de números.

Exemplo do Mundo Real: Um Emissor de Eventos com Tipagem Segura

Vamos construir um emissor de eventos simples que usa generics para mapear nomes de eventos para tipos de payload.

type EventMap = Record<string, any>;

class EventEmitter<T extends EventMap> {
  private listeners: { [K in keyof T]?: Array<(payload: T[K]) => void> } = {};

  on<K extends keyof T>(event: K, listener: (payload: T[K]) => void): void {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event]!.push(listener);
  }

  emit<K extends keyof T>(event: K, payload: T[K]): void {
    this.listeners[event]?.forEach(listener => listener(payload));
  }
}

// Uso
interface MyEvents {
  login: { userId: string };
  logout: { userId: string };
}

const emitter = new EventEmitter<MyEvents>();
emitter.on('login', (payload) => {
  console.log(payload.userId); // string
});
emitter.emit('login', { userId: '123' }); // OK
// emitter.emit('login', { userId: 123 }); // Erro

Esse padrão oferece total segurança de tipos para nomes de eventos e payloads, reduzindo erros em tempo de execução.

Armadilhas Comuns e Boas Práticas

Comparação: Generics vs. any vs. Tipos de União

Abordagem Segurança de Tipos Flexibilidade Caso de Uso
Generics Alta Alta Componentes reutilizáveis com tipos preservados
any Nenhuma Alta Prototipagem rápida, migração de JS
Tipos de União Média Média Conjunto limitado de tipos conhecidos

Generics equilibram flexibilidade e segurança, tornando-os ideais para bibliotecas e utilitários compartilhados.

Perguntas Frequentes

Quando devo usar generics em vez de tipos de união?

Use generics quando a relação de tipo entre entradas e saídas importa. Tipos de união servem para quando um valor pode ser um de um conjunto fixo de tipos, mas o tipo específico não precisa ser preservado entre operações.

Posso usar generics com arrow functions?

Sim, mas a sintaxe pode ser complicada em arquivos .tsx. Você pode escrever const identity = <T,>(arg: T): T => arg; com uma vírgula no final para evitar ambiguidade com JSX.

Como restringir um generic para aceitar apenas objetos?

Use T extends object ou T extends Record<string, unknown> para garantir que T seja um tipo de objeto.

Generics são uma pedra angular do sistema de tipos do TypeScript. Ao usá-los no seu código do dia a dia, você pode escrever funções, interfaces e classes que são ao mesmo tempo flexíveis e seguras. Comece com funções genéricas simples, depois incorpore gradualmente restrições e padrões mais complexos conforme necessário. Para mais ferramentas de desenvolvedor, confira nosso JSON Formatter para validar e formatar seus dados JSON rapidamente.