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

Frontend2026-10-04TryQuickToolBox

Você está escrevendo uma função que funciona com qualquer tipo, mas o TypeScript continua reclamando do any. Ou você copiou a mesma função três vezes só para lidar com tipos diferentes. Generics resolvem isso—eles permitem escrever código que funciona com vários tipos mantendo a segurança de tipos. Mas muitos desenvolvedores ou os evitam ou os usam em excesso. Este guia mostra como usar generics no código do dia a dia sem dor de cabeça.

O que são Generics e por que você deveria se importar?

Generics são uma forma de parametrizar tipos. Em vez de fixar um tipo como string ou number, você usa uma variável de tipo (geralmente T) que é substituída por um tipo concreto quando a função ou classe é usada.

Por que isso importa? Porque permite escrever código reutilizável que permanece seguro em termos de tipos. Sem generics, você duplicaria código ou usaria any, o que anula o propósito do TypeScript.

Funções genéricas: o bloco de construção

Vamos começar com um exemplo simples. Suponha que você queira uma função que retorne o primeiro elemento de um array.

function firstElement(arr: any[]): any {
  return arr[0];
}

Isso funciona, mas você perde a informação de tipo. Se você passar um array de strings, o tipo de retorno é any, não string. Agora com generics:

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

Agora o TypeScript sabe que, se você passar um string[], recebe de volta um string | undefined. O tipo é preservado.

Você também pode usar múltiplos parâmetros de tipo:

function pair<A, B>(a: A, b: B): [A, B] {
  return [a, b];
}

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 descrever estruturas de dados flexíveis.

interface Box<T> {
  value: T;
}

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

Isso é especialmente útil para respostas de API. Em vez de definir uma nova interface para cada endpoint, você pode definir um ApiResponse<T> genérico:

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

type User = { id: number; name: string };

const response: ApiResponse<User> = {
  data: { id: 1, name: "Alice" },
  status: 200,
};

Restrições genéricas: adicionando regras

Às vezes você quer restringir quais tipos podem ser usados com seu generic. É aí que entram as restrições. Use a palavra-chave extends para exigir que o tipo tenha certas propriedades.

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

getLength("hello"); // OK
getLength([1, 2, 3]); // OK
getLength(42); // Error: number doesn't have length

Restrições tornam seus generics mais previsíveis e evitam erros em tempo de execução.

Classes genéricas

Classes também podem ser genéricas. Isso é útil para estruturas de dados como pilhas, filas ou repositórios.

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

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

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

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

Quando usar generics (e quando não usar)

Generics são poderosos, mas podem tornar o código mais difícil de ler se usados em excesso. Aqui vai um guia rápido:

Use generics quando Evite generics quando
Você precisa preservar relações de tipo Um tipo união simples ou any já basta
Você está escrevendo utilitários reutilizáveis O tipo é sempre o mesmo
Você quer evitar duplicação de código Isso torna o código mais difícil de entender

Uma boa regra geral: se você se pegar escrevendo a mesma lógica para vários tipos, considere generics. Se você está adicionando um terceiro parâmetro de tipo e ninguém entende, dê um passo atrás.

Padrões comuns e boas práticas

Exemplo do mundo real: um cliente de API genérico

Vamos amarrar tudo com um exemplo prático: um cliente de API simples que busca dados e retorna uma resposta tipada.

async function fetchData<T>(url: string): Promise<T> {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  return response.json() as Promise<T>;
}

interface Post {
  userId: number;
  id: number;
  title: string;
  body: string;
}

async function main() {
  const post = await fetchData<Post>("https://jsonplaceholder.typicode.com/posts/1");
  console.log(post.title); // string
}

Esse padrão é comum em código frontend. Você obtém segurança de tipos sem se repetir para cada endpoint.

FAQ

O que a sintaxe <T> significa em TypeScript?

<T> declara um parâmetro de tipo. É um espaço reservado para um tipo que será especificado quando a função, interface ou classe for usada. Você pode nomeá-lo como quiser, mas T é convencional para um único tipo.

Posso usar múltiplos parâmetros de tipo genérico?

Sim. Você pode definir quantos precisar, separados por vírgulas: <T, U, V>. Use nomes descritivos para manter seu código legível.

Como restringir um generic para aceitar apenas objetos?

Use T extends object. Isso garante que T seja um tipo de objeto e não um primitivo como string ou number. Você também pode restringir a formas específicas, como T extends { id: number }.

Generics são parte central de escrever TypeScript sustentável. Se você trabalha com dados JSON e quer validá-los ou formatá-los rapidamente, experimente nosso JSON Formatter para formatar e inspecionar suas respostas de API.