Introducción a Generics en TypeScript
TypeScript es un lenguaje fuertemente tipado que extiende las capacidades de JavaScript agregando tipado estático. Sin embargo, una de sus características más potentes y flexibles es el mecanismo de Generics. Si alguna vez has escrito código que debe funcionar con diferentes tipos de datos, pero manteniendo la seguridad de tipos, entonces Generics es exactamente lo que necesitas.
Imagina que creas una función universal que puede aceptar arrays de números, strings u objetos. Sin generics, tendrías que usar el tipo any (lo que anula todos los beneficios de TypeScript) o escribir funciones separadas para cada tipo. Generics resuelve este problema de manera elegante y segura. En este artículo, analizaremos qué son los Generics, para qué sirven y cómo usarlos eficazmente en proyectos reales.
¿Qué son los Generics y para qué sirven?
Generics es un mecanismo que permite crear componentes (funciones, clases, interfaces) capaces de trabajar con diferentes tipos de datos sin perder información sobre los tipos. En lugar de fijar un tipo específico, se utiliza un parámetro de tipo que se sustituye en el momento de la llamada.
El problema sin Generics
Supongamos que quieres escribir una función que devuelva el primer elemento de un array. Sin generics, podría verse así:
function getFirstElement(arr: any[]): any { return arr[0];}
const num = getFirstElement([1, 2, 3]); // Tipo 'any' — perdimos la informaciónconsole.log(num.toUpperCase()); // Error en tiempo de ejecución, pero TypeScript no dice nadaAquí usamos any, y TypeScript no puede advertirnos que num es un número, no un string. Esto provoca posibles errores en tiempo de ejecución.
Solución con Generics
Ahora reescribamos la misma función usando generics:
function getFirstElement<T>(arr: T[]): T { return arr[0];}
const num = getFirstElement([1, 2, 3]); // Tipo 'number'const str = getFirstElement(['a', 'b', 'c']); // Tipo 'string'
// num.toUpperCase(); // Error de compilación — TypeScript sabe que num es un númeroEl símbolo <T> es el parámetro de tipo. Actúa como un placeholder: cuando llamamos a la función con un array de números, T se convierte en number, y el valor devuelto también obtiene el tipo number. De esta manera, mantenemos un tipado estricto sin perder flexibilidad.
Cómo usar Generics: escenarios principales
Los Generics no solo se aplican en funciones, sino también en clases, interfaces y tipos. Veamos los escenarios más comunes.
Generics en funciones
El caso más simple y frecuente son las funciones que trabajan con datos de diferentes tipos. Por ejemplo, una función para crear un par clave-valor:
function createPair<K, V>(key: K, value: V): { key: K; value: V } { return { key, value };}
const pair = createPair('id', 123); // { key: string; value: number }console.log(pair.key.toUpperCase()); // Funciona// pair.key.toFixed(); // Error — key es un stringAquí usamos dos parámetros de tipo: K para la clave y V para el valor. TypeScript deduce automáticamente los tipos basándose en los argumentos.
Generics en clases
Los generics son ideales para crear clases universales, como pilas, colas o almacenes de datos:
class Stack<T> { private items: T[] = [];
push(item: T): void { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }
peek(): T | undefined { return this.items[this.items.length - 1]; }}
const numberStack = new Stack<number>();numberStack.push(10);numberStack.push(20);console.log(numberStack