FNSP Web Page

Generics em TypeScript: quando são úteis e quando são ginástica

Há uma ideia errada sobre genéricos que explica quase todo o mau uso deles: a de que servem para aceitar qualquer coisa. Não servem. Para aceitar qualquer coisa há o unknown, e é mais honesto.

Um genérico serve para ligar tipos uns aos outros — dizer que a saída desta função depende do que lhe entrou, e que essas duas coisas são a mesma.

1. A regra que decide quase tudo

É esta, e resolve a maior parte das dúvidas sem discussão: se o parâmetro de tipo aparece uma só vez na assinatura, não está a ligar nada.

// O T aparece uma vez. Não liga a entrada a saída nenhuma,
// e podia ser simplesmente unknown[].
function contar<T>(lista: T[]): number {
  return lista.length;
}

// Aqui aparece duas vezes, e é isso que o justifica:
// o que sai é do mesmo tipo do que entrou.
function primeiro<T>(lista: T[]): T | undefined {
  return lista[0];
}

Antes de escrever <T>, vale a pena contar as ocorrências. Uma só, e o genérico está a enfeitar.

2. O caso que mais se encontra: o invólucro

É o primeiro dos dois genéricos que existem neste site. Todas as consultas públicas passam por uma função que lhes acrescenta cache:

const cacheada = <Args extends unknown[], R>(
  fn: (...args: Args) => Promise<R>,
  chave: string,
  etiquetas: string[],
) => unstable_cache(fn, [chave], { tags: etiquetas, revalidate: REVALIDACAO });

O que os dois parâmetros fazem é manter a assinatura intacta. A função que entra recebe uns argumentos e devolve uma promessa de alguma coisa; a que sai recebe os mesmos argumentos e devolve a mesma coisa.

Sem isso, cada consulta embrulhada perdia os tipos, e todos os sítios que a chamam passavam a trabalhar às escuras. O genérico aqui não acrescenta poder nenhum — serve para o invólucro ser invisível, que é exatamente o que se quer de um invólucro.

3. O outro caso: um componente para várias formas

A tabela da administração é a mesma para as frases, para os artigos e para as etiquetas — três registos com colunas completamente diferentes.

type Registo = { id: number } & Record<string, string | number>;

export function TabelaAdmin<R extends Registo>({
  registos,
  colunas,
}: {
  registos: R[];
  colunas: Coluna[];
}) { /* ... */ }

O que está a trabalhar ali não é o R — é o extends Registo. A restrição diz duas coisas ao mesmo tempo: qualquer forma serve, desde que tenha um id e valores que consigam atravessar para o cliente.

É essa a parte que costuma faltar. Um <T> sem restrição significa que não se sabe nada sobre T — e, não sabendo nada, também não se lhe pode fazer nada.

4. Quando é ginástica

  • Um genérico com um só sítio de uso. Se só há uma forma a passar por ali, escreva-se essa forma. O genérico é uma promessa de variedade que nunca se cumpre.

  • Um genérico para evitar uma união. Duas hipóteses conhecidas não são um genérico, são A | B.

  • Tipos condicionais e infer para resolver o que duas sobrecargas resolviam. A versão inteligente é mais curta e leva um dia a ser lida outra vez.

5. O teste que uso

Passar o rato por cima da chamada, no editor, e ler o que ele mostra. Não a definição — a chamada.

const artigo = primeiro(artigos);
// const artigo: Artigo | undefined        <- ganhou qualquer coisa

const x = transformar(dados);
// const x: Resultado<Entrada<T>, Chaves<T>> extends ... <- perdeu

Se o que aparece é um tipo que alguém consegue ler, o genérico está a pagar o que custa. Se é uma parede de sinais, passou a ser um problema de quem vier a seguir — e esse alguém é, quase sempre, nós próprios daqui a três meses.

6. Duas coisas pequenas que ajudam

Deixar o TypeScript inferir, em vez de escrever o tipo na chamada:

primeiro<Artigo>(artigos);  // desnecessário
primeiro(artigos);          // ele já sabe

E dar nomes aos parâmetros de tipo quando são mais do que um. O T passa; o T, U, V numa assinatura de cinco linhas não passa — Args e R custam o mesmo a escrever e dizem o que são.

O resumo

Um genérico justifica-se quando liga tipos: o que sai depende do que entra, e o compilador tem de saber disso. Se não liga nada, é decoração — e decoração em assinaturas cobra-se na primeira vez que alguém tenta perceber o que aquilo devolve.

Comentários

Ainda ninguém comentou este artigo.

Voltar ao blog

Gostávamos de saber quantas pessoas visitam o site, com o Google Analytics. Sem a sua autorização não corre nada. Política de Privacidade.