Introducción: ¿Qué son los Generics y por qué son importantes?
\n\nAl estudiar TypeScript, rápidamente te encuentras con situaciones donde necesitas escribir una función o clase que pueda trabajar con diferentes tipos de datos. Sin herramientas especiales, tendrías que duplicar el código para cada tipo o usar any, lo que elimina toda la magia del tipado estricto. Aquí es donde los Generics (genéricos o plantillas) vienen al rescate.
Generics es un potente mecanismo de TypeScript que permite crear componentes (funciones, clases, interfaces) que trabajan con varios tipos, manteniendo al mismo tiempo un tipado estricto. Imagina que escribes un contenedor universal que puede almacenar manzanas, libros y números, pero TypeScript sabe exactamente qué hay dentro en cada caso concreto.
\n\nEn este artículo, analizaremos para qué sirven los Generics, cómo declararlos y usarlos en proyectos reales. Aprenderás a hacer tu código más flexible, seguro y reutilizable.
\n\nFundamentos de Generics: sintaxis y primera función
\n\nComencemos con un ejemplo simple. Supongamos que necesitamos una función que recibe un argumento y lo devuelve (función identidad). Sin genéricos, escribiríamos así:
\n\nfunction identity(arg: number): number {\n return arg;\n}\n\nPero esta función solo funciona con números. Para hacerla universal, podemos usar any:
function identity(arg: any): any {\n return arg;\n}\n\nEl problema es que perdemos la información del tipo. TypeScript no sabe que devolverá exactamente el mismo tipo que se pasó. Aquí es donde los Generics entran en acción:
\n\nfunction identity<T>(arg: T): T {\n return arg;\n}\n\n// Uso:\nlet output1 = identity<string>("Hola"); // tipo string\nlet output2 = identity<number>(42); // tipo number\n\nAnalicemos la sintaxis:
\n- \n
<T>— es el parámetro de tipo. Puedes usar cualquier letra, pero por convención se suele usarT(Type),U,V. \n arg: T— el argumento tiene el tipoT. \n : Tdespués de los paréntesis — la función devuelve un valor de tipoT. \n
Ahora TypeScript sabe: si pasaste una cadena, la salida también será una cadena. Si es un número, será un número. La inferencia de tipos a menudo funciona automáticamente, por lo que puedes escribir simplemente identity("Hola") — TypeScript entenderá que T es string.
Generics en funciones: trabajo con arreglos y restricciones
\n\nLos genéricos se usan con frecuencia para trabajar con arreglos o colecciones. Consideremos una función que devuelve el primer elemento de un arreglo:
\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\nObserva: T[] es una notación abreviada para Array<T>. La función funciona con cualquier arreglo, pero devuelve un elemento exactamente del tipo que almacena el arreglo.
Restricciones (Constraints) con extends
\n\nA veces es necesario que el tipo T tenga ciertas propiedades. Por ejemplo, queremos obtener la longitud de un objeto. No todos los tipos tienen la propiedad length, por lo que podemos imponer una restricción:
interface HasLength {\n length: number;\n}\n\nfunction getLength<T extends HasLength>(arg: T): number {\n return arg.length;\n}\n\ngetLength("cadena"); // OK, la cadena tiene length\ngetLength([1, 2, 3]); // OK, el arreglo tiene length\ngetLength(42); // ¡Error! El número no tiene length