CAP 05 · LEC 04·TypeScript

Genéricos: tipos parametrizables

Los genéricos permiten escribir funciones, interfaces y clases que funcionan con cualquier tipo — sin perder la seguridad de tipos. Es la herramienta clave para escribir código reutilizable en TypeScript.

● AVANZADO14 min lectura4 ejerciciospor Fernando Herrera · actualizado mayo de 2026
¿Encontraste un error o algo que mejorar?Editá esta lección en GitHub →

El problema que resuelven

Sin genéricos, tendrías que duplicar código para cada tipo o perder la seguridad con any:

// ❌ Una función por tipo — duplicación function primerString(arr: string[]): string { return arr[0]; } function primerNumber(arr: number[]): number { return arr[0]; } // ❌ Con any — pierde la seguridad de tipos function primero(arr: any[]): any { return arr[0]; } const x = primero([1, 2, 3]); x.toUpperCase(); // Sin error en compilación, pero falla en runtime // ✅ Con genérico — reutilizable Y seguro function primerElemento<T>(arr: T[]): T { return arr[0]; } const num = primerElemento([1, 2, 3]); // TypeScript infiere: number const str = primerElemento(["a", "b"]); // TypeScript infiere: string // num.toUpperCase(); // ❌ Error en compilación — correcto num.toFixed(2); // ✅

Sintaxis básica

// Función genérica — T es el "parámetro de tipo" function identidad<T>(valor: T): T { return valor; } // TypeScript infiere T del argumento identidad(42); // T = number identidad("hola"); // T = string identidad([1, 2, 3]); // T = number[] // También puedes pasarlo explícitamente identidad<boolean>(true); // Múltiples parámetros de tipo function par<A, B>(a: A, b: B): [A, B] { return [a, b]; } const resultado = par("nombre", 25); // [string, number] console.log(resultado); // ["nombre", 25]
Salida["nombre", 25]

Restricciones (constraints)

Puedes restringir qué tipos acepta un genérico con extends:

// T debe tener la propiedad length function masCortoPrimero<T extends { length: number }>(a: T, b: T): T { return a.length <= b.length ? a : b; } console.log(masCortoPrimero("hola", "mundo")); // "hola" console.log(masCortoPrimero([1, 2], [1, 2, 3, 4])); // [1, 2] // masCortoPrimero(1, 2); // ❌ Error: number no tiene length // K debe ser una clave de T function obtenerProp<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const usuario = { nombre: "Ana", edad: 25 }; const nombre = obtenerProp(usuario, "nombre"); // string const edad = obtenerProp(usuario, "edad"); // number // obtenerProp(usuario, "email"); // ❌ "email" no existe en el tipo // Default type parameter function crearCaja<T = string>(valor?: T): { contenido: T | undefined } { return { contenido: valor }; } const cajaString = crearCaja(); // T = string (default) const cajaNumber = crearCaja(42); // T = number (inferido)
Salidahola [1, 2]

Interfaces y tipos genéricos

// Respuesta de API genérica interface ApiResponse<T> { data: T; status: number; mensaje?: string; } interface Usuario { id: number; nombre: string; } // Respuesta tipada — data es Usuario const respuestaUsuario: ApiResponse<Usuario> = { data: { id: 1, nombre: "Ana" }, status: 200, }; // Respuesta tipada — data es array de productos const respuestaLista: ApiResponse<Usuario[]> = { data: [{ id: 1, nombre: "Ana" }, { id: 2, nombre: "Luis" }], status: 200, }; // Stack genérico 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]; } get size(): number { return this.items.length; } } const stack = new Stack<number>(); stack.push(1); stack.push(2); console.log(stack.pop()); // 2 console.log(stack.size); // 1
Salida2 1

Practica