Hola a todos! El equipo de TestMace publica otra traducción del artículo del mundo del desarrollo web. Esta vez para principiantes! Que tengas una buena lectura.
Rompe el velo del misterio y la incomprensión sobre la sintaxis <T>
y finalmente hazte amigo de ella

Probablemente, solo los desarrolladores experimentados de Java u otros lenguajes fuertemente tipados no se sorprendan cuando ven el genérico en TypeScript. Su sintaxis es fundamentalmente diferente de todo lo que estamos acostumbrados a ver en JavaScript, por lo que no es tan fácil adivinar de inmediato lo que hace.
Me gustaría mostrarle que, de hecho, todo es mucho más simple de lo que parece. Demostraré que si puede implementar una función con argumentos en JavaScript, puede usar los genéricos sin ningún esfuerzo adicional. Vamos!
Genéricos en TypeScript
La documentación de TypeScript proporciona la siguiente definición: "Los genéricos son la capacidad de crear componentes que funcionan no solo con uno, sino con múltiples tipos de datos".
Wow! Entonces, la idea principal es que los genéricos nos permiten crear algunos componentes reutilizables que funcionan con varios tipos de datos que se les transmiten. ¿Pero cómo es esto posible? Aquí está lo que pienso.
Los genéricos y los tipos están relacionados entre sí, como los valores de función y los argumentos. Esta es una forma de decirle a los componentes (funciones, clases o interfaces) qué tipo usar al llamarlos, al igual que durante una llamada le decimos a la función qué valores usar como argumentos.
Es mejor entender esto usando el genérico de una función idéntica como ejemplo. Una función idéntica es una función que devuelve el valor del argumento que se le pasa. En JavaScript, se verá así:
identity.js function identity (value) { return value; } console.log(identity(1))
Hagamos que funcione con números:
Identity.ts function identity (value: Number) : Number { return value; } console.log(identity(1))
Bueno, agregamos un tipo a la definición de una función idéntica, pero nos gustaría que sea más flexible y que funcione para valores de cualquier tipo, y no solo para números. Para eso están los genéricos. Permiten a la función tomar valores de cualquier tipo de datos en la entrada y, dependiendo de ellos, transformar la función en sí.
genericIdentity.ts function identity <T>(value: T) : T { return value; } console.log(identity<Number>(1))
¡Oh, esa extraña sintaxis <T>
! Detén el pánico. Simplemente pasamos el tipo que queremos usar para una llamada de función específica.

Mira la foto de arriba. Cuando llama a la identity<Number>(1)
, el tipo Number
es el mismo argumento que 1. Se sustituye en todas partes por T
Una función puede tomar varios tipos de la misma manera que toma varios argumentos.

Mira la llamada a la función. Ahora la sintaxis genérica no debería asustarte. T
y U
son solo los nombres de las variables que te asignas a ti mismo. Cuando se llama a una función, se indican los tipos con los que funcionará esta función .
Una versión alternativa de comprender el concepto de genéricos es que transforman la función en función del tipo de datos especificado. La animación a continuación muestra cómo el registro de la función y el resultado devuelto cambian cuando se cambia el tipo.

Como puede ver, la función acepta cualquier tipo, lo que le permite crear componentes reutilizables de varios tipos, según lo prometido en la documentación.
Preste especial atención a la segunda llamada a console.log en la animación anterior: el tipo no se le pasa. En este caso, TypeScript intentará calcular el tipo a partir de los datos transmitidos.
Clases e interfaces genéricas
Ya sabe que los genéricos son solo una forma de pasar tipos a un componente. Acabas de ver cómo funcionan con funciones, y tengo buenas noticias: funcionan de la misma manera con clases e interfaces. En este caso, la indicación de tipo sigue al nombre de la interfaz o clase.
Mira un ejemplo y trata de resolverlo por ti mismo. Espero que hayas tenido éxito.
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())
Si el código no se comprende de inmediato, intente rastrear los valores de type
de arriba a abajo hasta las llamadas de función. El procedimiento es el siguiente:
- Se crea una nueva instancia de la clase
IdentityClass
y se le pasan el tipo de Number
y el valor 1
. - En la clase, al valor de
T
asigna el tipo Number
. IdentityClass
implementa GenericInterface<T>
, y sabemos que T
es Number
, y dicho registro es equivalente a GenericInterface<Number>
.- En
GenericInterface
U
genérica se convierte en Number
. En este ejemplo, utilicé intencionalmente diferentes nombres de variables para mostrar que el valor de tipo sube en la cadena, y el nombre de la variable no tiene significado.
Casos de uso reales: ir más allá de los tipos primitivos
En todos los insertos de código anteriores, se usaron tipos primitivos como Number
y string
. Por ejemplo, esto es lo máximo, pero en la práctica, es poco probable que use genéricos para tipos primitivos. Los genéricos serán realmente útiles cuando se trabaja con tipos o clases arbitrarias que forman un árbol de herencia.
Considere un ejemplo clásico de herencia. Digamos que tenemos una clase Car
, que es la base de las clases Truck
y Vespa
. Escribimos la función de utilidad washCar, que toma una instancia genérica de Car
y la devuelve.
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)
Al washCar
función washCar
que T extends Car
, indicamos qué funciones y propiedades podemos usar dentro de esta función. Generic también le permite devolver datos del tipo especificado en lugar del Car
habitual.
El resultado de este 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
Para resumir
Espero haberte ayudado a lidiar con los genéricos. Recuerde, todo lo que tiene que hacer es pasar el valor de type
a la función :)
Si desea leer más sobre los genéricos, he adjuntado un par de enlaces a continuación.
Que leer :