Menu

Generics em TypeScript: funções, tipos e classes genéricas

Generics permitem que uma função, interface, tipo ou classe funcione com muitos tipos mantendo-os ligados: o que entra decide o que sai. Veja funções genéricas, inferência de argumentos de tipo, vários type parameters, interfaces e classes genéricas, valores padrão e quando não usar generics.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Generics são type parameters. Uma função genérica declara um tipo marcador, normalmente chamado T, e cada chamada o preenche, então os tipos que entram decidem os tipos que saem. Uma única função passa a funcionar com números, strings e seus próprios objetos, e toda chamada continua totalmente tipada.

<T> depois do nome da função declara o type parameter. items: T[] o usa na entrada e T | undefined na saída, o que liga as duas. Você nunca escreveu number nem string: o TypeScript inferiu T a partir do argumento.

Por que não any ou unknown?

Sem generics, você tiparia o parâmetro como any ou unknown. Os dois aceitam qualquer array, e os dois perdem a ligação entre entrada e saída:

Tipo do parâmetroAceita qualquer entradaTipo do resultadoVerificado
anysimanynão: erros compilam
unknownsimunknownsim, mas você precisa fazer o narrowing antes de usar
T (generic)simligado à entradasim, com o tipo preciso

Inferência de argumentos de tipo

Normalmente o TypeScript infere os argumentos de tipo a partir dos valores que você passa. Você também pode escrevê-los explicitamente entre sinais de menor e maior na chamada, o que é necessário quando nada nos argumentos menciona T, ou quando a inferência escolhe algo que você não queria.

Dois resultados de inferência merecem atenção. pair(1, "one") sem o argumento explícito é um erro: o TypeScript infere T = number a partir do primeiro argumento e depois mostra Argument of type 'string' is not assignable to parameter of type 'number'. (TS2345). E emptyList() sem argumento nenhum recebe T = unknown, dando unknown[], o que raramente é o que você quer.

Vários type parameters

Uma função pode declarar quantos type parameters precisar, separados por vírgulas. Cada um é inferido de forma independente.

mapValues infere três coisas de uma vez: K é "tea" | "cake", V é number e R é string, a partir do retorno do callback. K extends string é uma restrição, tratada na próxima página.

Interfaces e type aliases genéricos

Tipos também podem receber parâmetros. Você então escreve o argumento ao usar o tipo: Box<number>, ApiResponse<User>. A maioria dos tipos de coleção nativos funciona assim: Array<T>, Map<K, V>, Promise<T>, Record<K, V>.

Result<T, E = string> também mostra um type parameter com valor padrão: Result<number> significa Result<number, string>. Como nos parâmetros opcionais de função, os parâmetros com padrão precisam vir depois dos obrigatórios.

Classes genéricas

Uma classe recebe type parameters depois do nome, e cada instância os fixa. Campos, métodos e parâmetros do construtor podem usá-los.

Se o construtor recebe um T, você pode omitir o argumento explícito: new Box(5) infere Box<number>. Membros estáticos pertencem à própria classe, não a uma instância, então não podem usar o type parameter da classe (Static members cannot reference class type parameters., TS2302).

Arrow functions e tipos de função genéricos

A lista de type parameters vai antes da lista de parâmetros. A mesma sintaxe descreve um tipo de função genérico.

const last = <T>(items: T[]): T | undefined => items[items.length - 1];

type Mapper = <T, R>(items: T[], fn: (item: T) => R) => R[];
const mapAll: Mapper = (items, fn) => items.map(fn);

// In a .tsx file, <T> looks like a JSX tag. Add a trailing comma:
const lastTsx = <T,>(items: T[]) => items[items.length - 1];

Inferindo literal types com const type parameters

Por padrão, um generic infere tipos alargados: pair("a", "b") dá [string, string]. Quando os valores exatos importam (nomes de rotas, nomes de eventos, listas de colunas), marque o parâmetro como const (TypeScript 5.0 e posteriores) e ele infere como se o argumento tivesse sido escrito as const:

Quem chama escreve um array comum, e a função mantém os literal types.

Quando não usar generics

Um type parameter vale a pena quando liga duas coisas: um parâmetro e o tipo de retorno, dois parâmetros, ou um parâmetro e um callback. Se T aparece só uma vez, ele não acrescenta nada, e um tipo simples é mais claro.

// Pointless: T is used once, so it is just a longer way to write unknown
function logValue<T>(value: T): void {
  console.log(value);
}

// Clearer
function logValueSimple(value: unknown): void {
  console.log(value);
}

// Also pointless: returns T but nothing connects T to an argument,
// so the caller is really just asserting a type
function parseJson<T>(text: string): T {
  return JSON.parse(text);
}

O último é comum e enganoso: parseJson<User>(text) parece seguro, mas não verifica nada, exatamente como JSON.parse(text) as User. Retorne unknown e valide. Outros sinais de exagero: um type parameter que sempre recebe o mesmo argumento, ou um que poderia ser trocado por uma union de dois tipos conhecidos.

Para generics que precisam saber algo sobre T (que ele tem um length, ou que K é uma chave de T), o próximo passo são as restrições com extends.

Perguntas frequentes

O que são generics no TypeScript?

Generics são type parameters: marcadores como T que são preenchidos com um tipo real cada vez que uma função, interface, type alias ou classe é usada. function first<T>(items: T[]): T | undefined funciona com qualquer array, e o resultado tem o tipo dos elementos do array que você passou, então first([1, 2]) é number | undefined e first(["a"]) é string | undefined.

Qual é a diferença entre generics e any?

O any desliga a verificação de tipos: o valor que entra e o valor que sai não têm relação, e o resultado também é any. Um generic mantém a relação: identity<T>(x: T): T retorna exatamente o tipo que você passou. Use unknown se você aceita qualquer coisa mas não precisa devolver o tipo, e um generic se precisa.

O que significa <T> no TypeScript?

Declara um type parameter chamado T. Em function wrap<T>(value: T), T é uma variável de tipo que o TypeScript preenche a partir do argumento em cada chamada, ou que você passa explicitamente como wrap<string>("a"). O nome T é só uma convenção; nomes mais longos como TItem ou Key funcionam do mesmo jeito.

Como escrever uma arrow function genérica no TypeScript?

Coloque a lista de type parameters antes dos parâmetros: const first = <T>(items: T[]): T | undefined => items[0];. Em um arquivo .tsx, <T> seria lido como uma tag JSX, então escreva <T,> com uma vírgula no final, ou <T extends unknown>.

Como definir um tipo padrão para um generic no TypeScript?

Adicione = Type depois do parâmetro: interface ApiResponse<T = unknown> { data: T }. Assim ApiResponse sem argumento significa ApiResponse<unknown>. Parâmetros com padrão precisam vir depois dos que não têm, como os parâmetros opcionais de função.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR