Introdução: O que são Generics e por que são importantes?
\n\nAo estudar TypeScript, você rapidamente se depara com situações em que precisa escrever uma função ou classe que possa trabalhar com diferentes tipos de dados. Sem ferramentas especiais, você teria que duplicar o código para cada tipo ou usar any, o que elimina toda a magia da tipagem estática. É aqui que os Generics (genéricos ou generalizações) entram em ação.
Generics são um mecanismo poderoso do TypeScript que permite criar componentes (funções, classes, interfaces) que trabalham com diferentes tipos, mantendo a tipagem estática. Imagine que você está escrevendo um contêiner universal que pode armazenar maçãs, livros e números, mas o TypeScript sabe exatamente o que está dentro em cada caso específico.
\n\nNeste artigo, vamos explorar para que servem os Generics, como declará-los e usá-los em projetos reais. Você aprenderá como tornar seu código mais flexível, seguro e reutilizável.
\n\nFundamentos dos Generics: sintaxe e primeira função
\n\nVamos começar com um exemplo simples. Suponha que precisamos de uma função que recebe um argumento e o retorna (função identidade). Sem genéricos, escreveríamos assim:
\n\nfunction identity(arg: number): number {\n return arg;\n}\n\nMas esta função funciona apenas com números. Para torná-la universal, podemos usar any:
function identity(arg: any): any {\n return arg;\n}\n\nO problema é que perdemos a informação sobre o tipo. O TypeScript não sabe que o retorno será exatamente o mesmo tipo que foi passado. É aqui que os Generics entram em cena:
\n\nfunction identity<T>(arg: T): T {\n return arg;\n}\n\n// Uso:\nlet output1 = identity<string>("Olá"); // tipo string\nlet output2 = identity<number>(42); // tipo number\n\nVamos analisar a sintaxe:
\n- \n
<T>— é o parâmetro de tipo. Você pode usar qualquer letra, mas por convenção, geralmente se usaT(Type),U,V. \n arg: T— o argumento tem o tipoT. \n : Tapós os parênteses — a função retorna um valor do tipoT. \n
Agora o TypeScript sabe: se você passou uma string, a saída também será uma string. Se passou um número, será um número. A inferência de tipos geralmente funciona automaticamente, então você pode escrever apenas identity("Olá") — o TypeScript entenderá que T é string.
Generics em funções: trabalhando com arrays e restrições
\n\nNa maioria das vezes, os genéricos são usados para trabalhar com arrays ou coleções. Vamos considerar uma função que retorna o primeiro elemento de um array:
\n\nfunction getFirst<T>(arr: T[]): T | undefined {\n return arr[0];\n}\n\nconst numbers = [1, 2, 3];\nconst firstNumber = getFirst(numbers); // tipo number\n\nconst strings = ["a", "b", "c"];\nconst firstString = getFirst(strings); // tipo string\n\nObserve: T[] é uma forma abreviada de Array<T>. A função funciona com qualquer array, mas retorna um elemento exatamente do tipo armazenado no array.
Restrições (Constraints) com extends
\n\nÀs vezes, precisamos que o tipo T tenha propriedades específicas. Por exemplo, queremos obter o comprimento de um objeto. Nem todos os tipos têm a propriedade length, então podemos impor uma restrição:
interface HasLength {\n length: number;\n}\n\nfunction getLength<T extends HasLength>(arg: T): number {\n return arg.length;\n}\n\ngetLength("string"); // OK, string tem length\ngetLength([1, 2, 3]); // OK, array tem length\ngetLength(42); // Erro! Número não tem length