Introducción a los Genéricos
Los genéricos permiten crear componentes que funcionan con varios tipos mientras mantienen la seguridad de tipos. Son como "plantillas" que puedes personalizar.
El Problema
Imagina que quieres crear una función que retorna el primer elemento de un array:
typescript
// Sin genéricos - pierde información de tipofunction primerElemento(arr: any[]): any { return arr[0];} const num = primerElemento([1, 2, 3]); // tipo: anyconst str = primerElemento(["a", "b"]); // tipo: any // ¡Perdimos la información de que num es number y str es string!La Solución: Genéricos
typescript
function primerElemento<T>(arr: T[]): T { return arr[0];} const num = primerElemento([1, 2, 3]); // tipo: numberconst str = primerElemento(["a", "b"]); // tipo: string // ¡TypeScript infiere el tipo correctamente!La T es un parámetro de tipo - un placeholder que será reemplazado por el tipo real cuando se llame a la función.
Sintaxis Básica
typescript
// Función genéricafunction identidad<T>(valor: T): T { return valor;} // Llamando - TypeScript infiere el tipoidentidad("texto"); // T = stringidentidad(42); // T = number // O especificando explícitamenteidentidad<boolean>(true);Múltiples Parámetros de Tipo
typescript
function par<K, V>(clave: K, valor: V): [K, V] { return [clave, valor];} const resultado = par("edad", 30); // [string, number]const otro = par(1, true); // [number, boolean]Interfaces Genéricas
typescript
interface Caja<T> { contenido: T;} const cajaDeTexto: Caja<string> = { contenido: "¡Hola!"}; const cajaDeNumero: Caja<number> = { contenido: 42};Clases Genéricas
typescript
class Pila<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 pilaNumeros = new Pila<number>();pilaNumeros.push(1);pilaNumeros.push(2);console.log(pilaNumeros.pop()); // 2 const pilaStrings = new Pila<string>();pilaStrings.push("a");pilaStrings.push("b");Restricciones (Constraints)
Limita qué tipos pueden usarse con extends:
typescript
// T debe tener la propiedad 'length'interface TieneLongitud { length: number;} function obtenerLongitud<T extends TieneLongitud>(item: T): number { return item.length;} obtenerLongitud("hello"); // OK - string tiene lengthobtenerLongitud([1, 2, 3]); // OK - array tiene lengthobtenerLongitud({ length: 10 }); // OK - objeto tiene length obtenerLongitud(42); // Error - number no tiene lengthUsando Parámetros de Tipo en Restricciones
typescript
function obtenerPropiedad<T, K extends keyof T>(obj: T, clave: K): T[K] { return obj[clave];} const persona = { nombre: "Pedro", edad: 30 }; obtenerPropiedad(persona, "nombre"); // OK, retorna stringobtenerPropiedad(persona, "edad"); // OK, retorna numberobtenerPropiedad(persona, "email"); // Error - 'email' no existeParámetros de Tipo por Defecto
typescript
interface Respuesta<T = any> { data: T; status: number;} // Usa tipo por defecto (any)const respuesta1: Respuesta = { data: "cualquier cosa", status: 200}; // Especifica el tipoconst respuesta2: Respuesta<Usuario> = { data: { nombre: "Pedro", edad: 30 }, status: 200};Ejemplo Práctico: Función de API
typescript
async function obtenerDatos<T>(url: string): Promise<T> { const response = await fetch(url); return response.json();} interface Usuario { id: number; nombre: string;} // TypeScript sabe el tipo de retornoconst usuario = await obtenerDatos<Usuario>("/api/usuario/1");console.log(usuario.nombre); // ¡El autocompletado funciona!Conclusión
Los genéricos son una herramienta poderosa para crear código flexible y con seguridad de tipos. Con ellos, puedes escribir componentes reutilizables sin sacrificar los beneficios del tipado estático.