Cuando trabajamos con TypeScript, los genéricos nos ofrecen una herramienta poderosa para crear funciones flexibles y seguras que pueden manejar distintos tipos de datos sin perder la tipificación. En particular, cuando diseñamos funciones, los genéricos nos permiten parametrizar el tipo de los argumentos y el tipo de retorno, lo que resulta especialmente útil en escenarios donde manejamos múltiples variantes de un mismo concepto.
Imaginemos que estamos desarrollando una API para una red social que permite distintos tipos de publicaciones. Por ejemplo, tenemos una publicación simple que es una nota con un mensaje de texto, otra que es una nota colorida que incluye un color y la posibilidad de subir fotos, y también publicaciones de vídeo que, además de una URL, incluyen la duración en segundos. Nuestra API tiene un único endpoint para subir cualquier tipo de publicación mediante un método HTTP POST.
Una forma inicial de abordar esto sería definir un tipo unión que englobe todas las variantes de publicación, y hacer que la función que sube la publicación acepte ese tipo. Sin embargo, esto implica que dentro de la función tendremos que hacer comprobaciones para saber qué tipo de publicación estamos manejando y actuar en consecuencia. Otra opción menos recomendable sería aceptar un tipo any, pero esto pierde toda la seguridad de tipos y puede llevar a errores al pasar datos no válidos.
Aquí es donde los genéricos en funciones nos ofrecen una solución elegante. Podemos parametrizar la función que sube la publicación con un tipo genérico Publicacion, de modo que la función acepte un argumento de ese tipo y también devuelva un valor del mismo tipo. Esto nos permite mantener la flexibilidad para manejar cualquier tipo de publicación, pero con la seguridad de que el tipo se mantiene consistente.
El código para esta función genérica podría ser algo así:
function subir<Publicacion>(p: Publicacion): Publicacion {
return p;
}
Cuando llamamos a esta función, podemos dejar que TypeScript infiera el tipo automáticamente a partir del argumento que le pasamos. Por ejemplo, si tenemos un objeto nota con un mensaje, al llamar a subir(nota), TypeScript entiende que el tipo genérico Publicacion es el tipo de nota, y por tanto el valor devuelto también será de ese tipo.
type Nota = {
mensaje: string;
};
const nota: Nota = { mensaje: "Hola, mundo" };
const resultado = subir(nota);
// TypeScript infiere que resultado es de tipo Nota
Esto nos evita tener que especificar explícitamente el tipo genérico al llamar a la función, haciendo el código más limpio y aprovechando la inferencia de tipos que ofrece TypeScript.
Además, podemos extender esta idea para que la función acepte múltiples parámetros genéricos si fuera necesario, por ejemplo, subir<Publicacion, Extra>(p: Publicacion, extra: Extra), lo que nos da aún más flexibilidad para manejar distintos tipos de datos relacionados.
Sin embargo, hay que tener en cuenta que esta flexibilidad también puede ser un arma de doble filo. Si no restringimos el tipo genérico, cualquiera podría pasar un valor que no tenga sentido para nuestra API, como un número o un string simple, y aunque TypeScript lo acepte, la API probablemente rechazaría ese dato. Por eso, en escenarios reales, conviene combinar los genéricos con restricciones que limiten los tipos permitidos, asegurando que solo se puedan subir publicaciones válidas.
En definitiva, los genéricos en funciones nos permiten crear APIs más robustas y reutilizables, manteniendo la seguridad de tipos y facilitando la gestión de múltiples variantes de datos sin duplicar código ni perder claridad.