Entender el tema de los tipos genéricos en TypeScript podría resultar desafiante si no tenemos una idea clara de cómo funcionan o cómo podemos usarlos para nuestro beneficio. El propio Matt Pocock, a través de este hilo muy informativo al que, con su permiso, vamos a traduccir para el beneficio de la comunidad hispanoparlante, explica los fundamentos básicos para poder comprender esta capacidad dentro de TypeScript.

En Typescript, lo que podemos interpretar como tipos genéricos en realidad lo podemos representar en tres conceptos fundamentales:

1. Pasar tipos como argumentos

2. Pasar tipos a funciones

3. Inferir tipos a partir de los argumentos de las funciones

Pasando tipos como argumentos

Comencemos evaluando el primer concepto, pasar tipos como argumentos.

En TypeScript, podemos declarar un tipo que represente un objeto, una propiedad primitiva, o incluso una función. Sinceramente, puedes representar lo que desees.

Pero supongamos que necesitas crear algunos tipos con una estructura similar, como la estructura de ciertos datos o el resultado de un API.

El siguiente código no sigue el principio DRY (Don’t Repeat Yourself, que significa ‘no te repitas’), un principio de desarrollo de software que tiene como objetivo reducir la repetición de patrones y la duplicación de código en favor de abstracciones y evitar la redundancia.) ¿Podemos optimizarlo entonces?

Si tienes inclinación hacia la programacion orientada a objectos (OOP), puedes simplificarlo ligeramente utilizando una interfaz reutilizable…

Sin embargo, sería aún más conciso utilizar una «función tipada» que tome el tipo que define nuestros datos y devuelva la nueva estructura de los mismos.

Es importante entender esta sintaxis, ya que la utilizaremos más adelante.

Los corchetes angulares – <TData> – indican en TypeScript que deseamos agreagar un argumento de un tipo especifico a este tipo.

Podemos darle cualquier nombre a TData; piénsalo como un argumento para una función.

Este constituye un tipo genérico.

Los tipos genéricos pueden aceptar múltiples argumentos tipados.

Puedes asignar valores predeterminados a los argumentos tipados.

Incluso puedes aplicar restricciones en los argumentos tipados para limitar los tipos que se pueden usar.

«Pero, ya sabes, tengo que mantener este hilo en movimiento.», El propio Matt reconoce que por aquí hay un rabbit hole que debemos evitar por el momento.

¿Y si te dijera que no solo podrías pasar tipos a tipos?

Pasando tipos a funciones

Así es, al igual que los tipos pueden aceptar argumentos tipados, las funciones también pueden hacerlo.

En el ejemplo anterior, agregamos un <T> antes de los paréntesis de la función createSet al ser declarada.

Luego, pasamos manualmente ese <T> a Set(), lo que a su vez te permite pasar un argumento tipado.

Eso significa que cuando invocamos la expresión, podemos pasar un argumento de tipo <string> a createSet.

Como resultado, obtenemos un conjunto (Set) al que solo podemos agregar valores de tipo string.

Esta es la segunda idea que la gente quiere expresar cuando habla de genéricos: pasar tipos de manera manual a funciones.

Probablemente lo hayas visto si usas React, y has necesitado pasar un argumento tipado al usar useState.

Sin embargo, también habrás notado otro comportamiento en React.

En ciertos casos, no es necesario pasar explícitamente el argumento tipado, ya que puede ser inferido automáticamente…

Inferir tipos a partir de los argumentos de las funciones

Ahora, echemos otroz vistazo a nuestra función createSet.

Notarás que en realidad no acepta argumentos, solo argumentos tipados.

Pero, ¿qué sucede si modificamos nuestra función para que acepte un valor inicial para el conjunto?

Dado que el valor inicial debe ser del mismo tipo que el conjunto, definamos el tipo de ese valor inicial como T.

Ahora, cuando utilicemos la función, necesitaremos proporcionar un valor inicial que sea del mismo tipo que el argumento tipdado que pasamos a createSet.

Pero aquí es donde ocurre la magia. TypeScript puede inferir el tipo de «T» a partir del valor «inicial».

En otras palabras, el argumento tipado será deducido del argumento disponible en tiempo de ejecución.

Puedes verificar esto al pasar el cursor sobre una de las llamadas a createSet. He creado un breve ejemplo en el Playground de TypeScript para que lo veas por ti mismo. Observarás que se infiere <string> cuando le pasamos un nombre o cualquier string a la función.

Lo mismo ocurre con useState en React; cuando tengas la oportunidad, consulta la sintaxis useState<number> en las tooltips de tu editor favorito.

Ahora, repasemos la función objKeys que presenté al principio del artículo.

Esta función también tiene varios elementos interesantes adicionales:

– Restringimos que T sea un objeto para que pueda ser pasado a Object.keys (que solo acepta objetos)

– Forzamos el tipo de retorno de Object.keys sea Array<keyof T>

Lo que usualmente consideramos como «genéricos» son en realidad varios diferentes patrones:

– Pasar tipos a tipos – DataShape<T>

– Pasar tipos a funciones – createSet<string>()

– Inferir tipos de argumentos pasados a funciones – useState(0)

Si este final te ha dejado con ganas de explorar más sobre este tema y profundizar, realmente te recomiendo el contenido de Matt; descubrirás todo el potencial asombroso detrás de TypeScript.

Referencias

https://www.telerik.com/blogs/easily-understand-typescript-generics

https://rossbulat.medium.com/typescript-generics-explained-15c6493b510f

https://betterprogramming.pub/typescript-generics-90be93d8c292