Generics en TypeScript: para qué sirven y cómo usarlos
TypeScript es una herramienta poderosa que añade tipado estático a JavaScript. Sin embargo, si estás empezando a aprender TypeScript, es posible que te hayas encontrado con situaciones en las que tienes que escribir el mismo código para diferentes tipos de datos. Aquí es donde los Generics (tipos genéricos) vienen al rescate. En este artículo, analizaremos qué son los Generics, para qué sirven y cómo usarlos en la práctica.
¿Qué son los Generics y por qué son importantes?
Los Generics son un mecanismo de TypeScript que permite crear componentes (funciones, clases, interfaces) que funcionan con diferentes tipos de datos, en lugar de con uno solo específico. Imagina que escribes una función que debe funcionar tanto con números, como con cadenas y arrays. Sin genéricos, tendrías que usar el tipo any (lo que anula la esencia de TypeScript) o crear varias copias de la misma función.
Principales ventajas de los Generics:
- Type-safe — mantienes un tipado estricto, pero el código sigue siendo flexible.
- Reutilización de código — una misma función o clase puede trabajar con diferentes tipos.
- Legibilidad — el código se vuelve más claro y autodocumentado.
Sintaxis básica de Generics
Comencemos con el ejemplo más simple. Supongamos que necesitamos una función que devuelva el argumento que recibe. Sin Generics, escribiríamos así:
function identity(arg: number): number { return arg;}Pero, ¿qué pasa si necesitamos pasar una cadena? Tendríamos que crear una nueva función. Con Generics lo hacemos así:
function identity<T>(arg: T): T { return arg;}
// Uso:let output1 = identity<string>("Hola");let output2 = identity<number>(42);let output3 = identity<boolean>(true);Aquí, <T> es un parámetro de tipo (o variable de tipo). Cuando llamamos a la función, TypeScript sustituye automáticamente el tipo necesario. Puedes usar cualquier letra: T, U, V o incluso nombres descriptivos como Type.
Por cierto, TypeScript puede inferir el tipo por sí mismo, por lo que a menudo se puede escribir sin indicarlo explícitamente:
let output = identity("Hola"); // TypeScript entiende que T = stringTrabajo con arrays y múltiples tipos
Una de las aplicaciones más comunes de los Generics es trabajar con arrays. Supongamos que queremos escribir una función que recibe un array y devuelve el primer elemento:
function getFirstElement<T>(arr: T[]): T | undefined { return arr[0];}
const numbers = [1, 2, 3];const firstNumber = getFirstElement(numbers); // tipo number | undefined
const strings = ["a", "b", "c"];const firstString = getFirstElement(strings); // tipo string | undefinedObserva: usamos T[], que es lo mismo que Array<T>. ¿Y si la función recibe dos tipos diferentes? ¡Fácil!
function merge<T, U>(obj1: T, obj2: U): T & U { return { ...obj1, ...obj2 };}
const result = merge({ name: "Ana" }, { age: 25 });// result tiene el tipo { name: string; } & { age: number; }// En la práctica: { name: string; age: number; }Restricciones de Generics (Constraints)
A veces es necesario que el genérico funcione solo con tipos que tengan ciertas propiedades. Para ello se usan restricciones con la palabra clave extends.
Por ejemplo, queremos escribir una función que obtenga la longitud de un objeto. Obviamente, no todos los tipos tienen la propiedad length. Verifiquemos esto:
function getLength<T extends { length: number