Introducción a los Generics de TypeScript
TypeScript es un lenguaje de programación fuertemente tipado que añade tipado estático a JavaScript. Una de las capacidades más potentes y a menudo subestimadas de TypeScript son los Generics (genéricos, tipos genéricos). Si alguna vez has escrito una función que debe funcionar con diferentes tipos de datos, o has creado un componente que debe ser universal, entonces los Generics son exactamente lo que necesitas.
En este artículo, analizaremos en detalle qué son los genéricos en TypeScript, para qué sirven y cómo usarlos en la práctica. Aprenderás cómo hacer tu código más flexible, seguro y reutilizable.
¿Qué son los Generics y para qué sirven?
Generics (tipos genéricos) son un mecanismo que permite crear componentes (funciones, clases, interfaces) que trabajan con diferentes tipos de datos, sin perder la información sobre el tipo. Imagina que escribes una función que devuelve el primer elemento de un array. Sin genéricos, tendrías que usar el tipo any, lo que te priva de las ventajas del tipado fuerte.
¿Por qué usar Generics?
- Seguridad de tipos: TypeScript verifica los tipos en tiempo de compilación, previniendo errores.
- Reutilización de código: Una misma función o clase puede funcionar con cualquier tipo de dato.
- Mejora de la legibilidad: El código se vuelve más comprensible y autodocumentado.
- Reducción de duplicación: No necesitas escribir funciones separadas para cada tipo de dato.
Sin Generics, escribirías algo como esto:
// Mal: perdemos la información del tipofunction getFirstElement(arr: any[]): any { return arr[0];}
const num = getFirstElement([1, 2, 3]); // Tipo anyconsole.log(num.toUpperCase()); // ¡Error en tiempo de ejecución!Con Generics, el código se vuelve seguro y flexible:
// Bien: conservamos el tipofunction getFirstElement<T>(arr: T[]): T | undefined { return arr[0];}
const num = getFirstElement([1, 2, 3]); // Tipo numberconst str = getFirstElement(['a', 'b', 'c']); // Tipo string
// num.toUpperCase(); // ¡Error de compilación! number no tiene el método toUpperCaseFundamentos de la sintaxis de Generics
Sintaxis con corchetes angulares
Los Generics usan corchetes angulares <> para indicar los parámetros de tipo. Generalmente, los parámetros se denotan con una sola letra: T (Type), U, V, etc. Sin embargo, puedes usar cualquier nombre con significado.
// Ejemplo más simple: función con un parámetro de tipofunction identity<T>(arg: T): T { return arg;}
// Indicación explícita del tipolet result1 = identity<string>('Hello');
// Inferencia de tipo (TypeScript determina el tipo automáticamente)let result2 = identity(42); // Tipo numberMúltiples parámetros de tipo
Puedes usar varios parámetros de tipo en una misma función o clase:
function merge<T, U>(obj1: T, obj2: U): T & U { return { ...obj1, ...obj2 };}
const merged = merge({ name: 'John' }, { age: 30 });// Tipo: { name: string } & { age: number }console.log(merged.name); // Johnconsole.log(merged.age); // 30Restricciones (Constraints)
A veces es necesario restringir los tipos que pueden ser pasados a un genérico. Para esto se usa la palabra clave extends:
interface HasLength { length: number;}
// La función solo funciona con tipos que tienen la propiedad lengthfunction logLength<T extends HasLength>(arg: T): T { console.log(arg.length); return arg;}
logLength('Hello'); // Funciona: 5logLength([1, 2, 3]); // Funciona: 3// logLength(123); // ¡Error! number no tiene length