Olá pessoal! A equipe do TestMace publica outra tradução do artigo do mundo do desenvolvimento web. Desta vez para iniciantes! Boa leitura.
Quebrar o véu de mistério e mal-entendido sobre a sintaxe <T>
e finalmente fazer amizade com ele

Provavelmente, apenas desenvolvedores experientes de Java ou outras linguagens fortemente tipadas não surgem quando vêem o genérico no TypeScript. Sua sintaxe é fundamentalmente diferente de tudo o que estamos acostumados a ver no JavaScript, portanto, não é tão fácil adivinhar imediatamente o que ele faz.
Gostaria de mostrar que, de fato, tudo é muito mais simples do que parece. Vou provar que, se você conseguir implementar uma função com argumentos em JavaScript, poderá usar genéricos sem nenhum esforço extra. Vamos lá!
Genéricos em TypeScript
A documentação do TypeScript fornece a seguinte definição: "Genéricos são a capacidade de criar componentes que funcionam não apenas com um, mas com vários tipos de dados".
Uau! Portanto, a idéia principal é que os genéricos nos permitam criar alguns componentes reutilizáveis que funcionam com vários tipos de dados transmitidos a eles. Mas como isso é possível? Aqui está o que eu penso.
Genéricos e tipos estão relacionados entre si, como valores e argumentos de função. Essa é uma maneira de dizer aos componentes (funções, classes ou interfaces) que tipo usar ao chamá-los, assim como durante uma chamada, dizemos à função quais valores usar como argumentos.
É melhor entender isso usando o genérico de uma função idêntica como exemplo. Uma função idêntica é uma função que retorna o valor do argumento passado para ele. Em JavaScript, ficará assim:
identity.js function identity (value) { return value; } console.log(identity(1))
Vamos fazê-lo funcionar com números:
identity.ts function identity (value: Number) : Number { return value; } console.log(identity(1))
Bem, adicionamos um tipo à definição de uma função idêntica, mas gostaríamos que fosse mais flexível e funcionasse para valores de qualquer tipo, e não apenas para números. É para isso que servem os genéricos. Eles permitem que a função obtenha valores de qualquer tipo de dados na entrada e, dependendo deles, transforme a própria função.
genericIdentity.ts function identity <T>(value: T) : T { return value; } console.log(identity<Number>(1))
Oh, essa estranha sintaxe <T>
! Pare o pânico. Acabamos de passar o tipo que queremos usar para uma chamada de função específica.

Veja a foto acima. Quando você chama a identity<Number>(1)
, o tipo Number
é o mesmo argumento que 1. Ele é substituído em todos os lugares por T
Uma função pode assumir vários tipos da mesma maneira que requer vários argumentos.

Veja a chamada de função. Agora, a sintaxe genérica não deve assustá-lo. T
e U
são apenas os nomes das variáveis que você atribui a si mesmo. Quando uma função é chamada, os tipos com os quais essa função funcionará são indicados .
Uma versão alternativa para entender o conceito de genéricos é que eles transformam a função dependendo do tipo de dados especificado. A animação abaixo mostra como o registro da função e o resultado retornado mudam quando o tipo é alterado.

Como você pode ver, a função aceita qualquer tipo, o que permite criar componentes reutilizáveis de vários tipos, conforme prometido na documentação.
Preste atenção especial à segunda chamada para console.log na animação acima - o tipo não é passado para ela. Nesse caso, o TypeScript tentará calcular o tipo a partir dos dados transmitidos.
Classes e interfaces genéricas
Você já sabe que os genéricos são apenas uma maneira de passar tipos para um componente. Você acabou de ver como eles funcionam com funções, e eu tenho boas notícias: eles funcionam da mesma maneira com classes e interfaces. Nesse caso, a indicação do tipo segue o nome da interface ou classe.
Veja um exemplo e tente descobrir por si mesmo. Espero que você tenha conseguido.
genericClass.ts interface GenericInterface<U> { value: U getIdentity: () => U } class IdentityClass<T> implements GenericInterface<T> { value: T constructor(value: T) { this.value = value } getIdentity () : T { return this.value } } const myNumberClass = new IdentityClass<Number>(1) console.log(myNumberClass.getIdentity())
Se o código não for entendido imediatamente, tente rastrear os valores de type
de cima para baixo, até chamadas de função. O procedimento é o seguinte:
- Uma nova instância da classe
IdentityClass
é criada e o tipo Number
e o valor 1
são passados para ela. - Na classe, o valor de
T
atribuído ao tipo Number
. IdentityClass
implementa GenericInterface<T>
, e sabemos que T
é Number
, e esse registro é equivalente a GenericInterface<Number>
.- No
GenericInterface
U
genérico se torna Number
. Neste exemplo, usei intencionalmente diferentes nomes de variáveis para mostrar que o valor do tipo sobe na cadeia e o nome da variável não tem significado.
Casos de uso reais: vão além dos tipos primitivos
Em todas as inserções de código acima, tipos primitivos como Number
e string
foram usados. Por exemplo, esse é o máximo, mas , na prática, é improvável que você use genéricos para tipos primitivos. Os genéricos serão realmente úteis ao trabalhar com tipos ou classes arbitrários que formam uma árvore de herança.
Considere um exemplo clássico de herança. Digamos que temos uma classe Car
, que é a base das classes Truck
e Vespa
. Escrevemos a função de utilitário washCar, que pega uma instância genérica de Car
e a retorna.
car.ts class Car { label: string = 'Generic Car' numWheels: Number = 4 horn() { return "beep beep!" } } class Truck extends Car { label = 'Truck' numWheels = 18 } class Vespa extends Car { label = 'Vespa' numWheels = 2 } function washCar <T extends Car> (car: T) : T { console.log(`Received a ${car.label} in the car wash.`) console.log(`Cleaning all ${car.numWheels} tires.`) console.log('Beeping horn -', car.horn()) console.log('Returning your car now') return car } const myVespa = new Vespa() washCar<Vespa>(myVespa) const myTruck = new Truck() washCar<Truck>(myTruck)
washCar
função washCar
que T extends Car
, indicamos quais funções e propriedades podemos usar dentro dessa função. Genérico também permite retornar dados do tipo especificado em vez do Car
habitual.
O resultado desse código será:
Received a Vespa in the car wash. Cleaning all 2 tires. Beeping horn - beep beep! Returning your car now Received a Truck in the car wash. Cleaning all 18 tires. Beeping horn - beep beep! Returning your car now
Resumir
Espero ter ajudado você a lidar com genéricos. Lembre-se, basta passar o valor do type
para a função :)
Se você quiser ler mais sobre genéricos, anexei alguns links abaixo.
O que ler :