Generics
Escrever uma função/tipo que funciona com qualquer tipo, sem perder a checagem — o <T> que aparece por toda API tipada.
O problema que generics resolve
// versão sem generics — funciona, mas perde informação de tipo
function primeiroItem(lista: any[]): any {
return lista[0];
}
const numero = primeiroItem([1, 2, 3]);
numero.toFixed(2); // TS não reclama... mas também não confirma que "numero" é number de verdade// com generics — o tipo de entrada "vaza" pro tipo de saída
function primeiroItem<T>(lista: T[]): T {
return lista[0];
}
const numero = primeiroItem([1, 2, 3]); // T é inferido como number
const nome = primeiroItem(["Ana", "Bia"]); // T é inferido como string
numero.toFixed(2); // ok, TS sabe que é number
nome.toUpperCase(); // ok, TS sabe que é string<T> é um "parâmetro de tipo" — um espaço reservado que só se torna um tipo concreto quando a função é chamada. É a mesma ideia de um parâmetro normal, só que pra tipo em vez de valor.
Generics em interface — o formato de uma resposta de API
interface RespostaApi<T> {
dado: T;
status: number;
}
const respostaUsuario: RespostaApi<{ nome: string }> = {
dado: { nome: "Ana" },
status: 200,
};
const respostaLista: RespostaApi<string[]> = {
dado: ["a", "b", "c"],
status: 200,
};Isso evita escrever RespostaApiDeUsuario, RespostaApiDeLista, RespostaApiDeProduto... — uma interface genérica cobre todos os formatos de resposta, mudando só o que vai dentro de dado.
Restringindo o generic com extends
function pegarNome<T extends { nome: string }>(item: T): string {
return item.nome;
}
pegarNome({ nome: "Ana", idade: 28 }); // ok — tem "nome"
pegarNome({ idade: 28 });
// Error: Property 'nome' is missing<T extends { nome: string }> diz: "T pode ser qualquer tipo, contanto que tenha pelo menos um campo nome do tipo string." Sem isso, o TS não deixaria acessar item.nome (T poderia ser qualquer coisa, inclusive algo sem esse campo).
Exercício
Escreva uma função genérica ultimoItem que retorna o último elemento de um array de qualquer tipo.