Genéricos en TypeScript: cuándo y por qué usarlos

Los genéricos son una de esas características de TypeScript que, bien usadas, pueden transformar la forma en la que estructuramos nuestras funciones y componentes. En este artículo vamos a ver cuándo y por qué deberías incorporarlos en tu código, sin caer en la trampa de utilizarlos por gusto propio.
¿Qué son los genéricos?
En esencia, los genéricos son parámetros de tipo que se definen en tiempo de compilación. En lugar de fijar un tipo concreto, declaras una variable de tipo que se rellenará cuando la función o clase sea invocada.
function identity<T>(value: T): T {
return value;
}En el ejemplo anterior, T actúa como un placeholder que puede ser cualquier tipo que el llamador elija.
Ventajas principales
- Tipado fuerte: Evitas errores en tiempo de ejecución porque el compilador verifica que los tipos coinciden.
- Reutilización: Una única implementación sirve para múltiples tipos, reduciendo la duplicación de código.
- Autocompletado mejorado: Los editores pueden ofrecer sugerencias más precisas cuando los tipos genéricos están bien definidos.
- Documentación implícita: El propio código explica qué tipos se esperan, lo que facilita la lectura a otros desarrolladores.
Cuándo deberías usarlos
Los genéricos no son una solución mágica para todo. Aquí tienes algunos casos típicos donde su uso aporta valor real:
- Funciones utilitarias que operan sobre colecciones (
Array,Map, etc.) y necesitan preservar el tipo de los elementos. - Componentes de UI en librerías como React, donde las props pueden variar pero la lógica interna sigue siendo la misma.
- Clases de servicios que gestionan datos de diferentes modelos (por ejemplo, un
Repositorygenérico). - APIs externas que devuelven datos cuyo formato depende de la petición (por ejemplo,
fetchcon tipos de respuesta dinámicos).
Si tu función o clase sólo maneja un tipo concreto y no hay intención de ampliarla, usar genéricos puede añadir complejidad innecesaria.
Ejemplo práctico: un hook genérico en React
Supongamos que quieres crear un hook que gestione el estado de una lista de elementos cualquiera. Con genéricos, el hook será reutilizable para Todo, User, Product… sin perder tipado.
import { useState } from 'react';
function useList<T>(initial: T[] = []) {
const [items, setItems] = useState<T[]>(initial);
const add = (item: T) => setItems(prev => [...prev, item]);
const remove = (index: number) =>
setItems(prev => prev.filter((_, i) => i !== index));
const clear = () => setItems([]);
return { items, add, remove, clear };
}
// Uso en un componente
interface Todo {
id: number;
text: string;
done: boolean;
}
function TodoList() {
const { items, add, remove, clear } = useList<Todo>();
// ... UI del componente
return null;
}El hook useList mantiene el tipado de T en todas sus operaciones, por lo que TypeScript nos avisará si intentamos añadir un User a una lista de Todo.
Errores comunes al usar genéricos
- Olvidar restringir el tipo: Si tu función necesita que el tipo tenga ciertas propiedades, usa
extendspara crear una restricción, por ejemplo<T extends { id: number }>. - Demasiada generalidad: Definir genéricos en lugares donde no aportan valor puede dificultar la lectura y el debugging.
- Tipos implícitos demasiado amplios: Cuando el compilador no puede inferir el tipo, termina asumiendo
any. Siempre declara explícitamente el tipo al usar la función si la inferencia falla.
Buenas prácticas resumidas
- Usa genéricos cuando la lógica sea independiente del tipo concreto.
- Restringe los genéricos con
extendssi necesitas garantizar ciertas propiedades. - Prefiere la inferencia automática, pero no temas anotar el tipo en la llamada para mayor claridad.
- Mantén la firma de la función lo más simple posible; evita encadenar varios genéricos sin necesidad.
En conclusión, los genéricos son una herramienta poderosa que, bien aplicada, mejora la seguridad y la reutilización del código en TypeScript. Identifica los puntos donde tu lógica es agnóstica al tipo y verás cómo tu base de código se vuelve más limpia y robusta.