Genéricos de TypeScript para el día a día: Guía práctica

Frontend2026-10-04TryQuickToolBox

Estás escribiendo una función que funciona con cualquier tipo, pero TypeScript sigue quejándose de any. O has copiado la misma función tres veces solo para manejar diferentes tipos. Los genéricos resuelven esto: te permiten escribir código que funciona con múltiples tipos mientras preserva la seguridad de tipos. Pero muchos desarrolladores los evitan o los usan en exceso. Esta guía te muestra cómo usar genéricos en el código diario sin dolores de cabeza.

¿Qué son los genéricos y por qué deberían importarte?

Los genéricos son una forma de parametrizar tipos. En lugar de codificar un tipo como string o number, usas una variable de tipo (a menudo T) que se reemplaza con un tipo concreto cuando se usa la función o clase.

¿Por qué es importante? Porque te permite escribir código reutilizable que sigue siendo seguro en cuanto a tipos. Sin genéricos, tendrías que duplicar código o usar any, lo que anula el propósito de TypeScript.

Funciones genéricas: el bloque de construcción

Empecemos con un ejemplo simple. Supongamos que quieres una función que devuelva el primer elemento de un array.

function firstElement(arr: any[]): any {
  return arr[0];
}

Esto funciona, pero pierdes información de tipo. Si pasas un array de strings, el tipo de retorno es any, no string. Ahora con genéricos:

function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

Ahora TypeScript sabe que si pasas un string[], obtienes un string | undefined de vuelta. El tipo se preserva.

También puedes usar múltiples parámetros de tipo:

function pair<A, B>(a: A, b: B): [A, B] {
  return [a, b];
}

Interfaces genéricas y alias de tipo

Los genéricos no se limitan a funciones. Puedes crear interfaces genéricas y alias de tipo para describir estructuras de datos flexibles.

interface Box<T> {
  value: T;
}

const stringBox: Box<string> = { value: "hello" };
const numberBox: Box<number> = { value: 42 };

Esto es especialmente útil para respuestas de API. En lugar de definir una nueva interfaz para cada endpoint, puedes definir una ApiResponse<T> genérica:

interface ApiResponse<T> {
  data: T;
  status: number;
}

type User = { id: number; name: string };

const response: ApiResponse<User> = {
  data: { id: 1, name: "Alice" },
  status: 200,
};

Restricciones genéricas: añadir reglas

A veces quieres restringir qué tipos se pueden usar con tu genérico. Ahí es donde entran las restricciones. Usa la palabra clave extends para requerir que el tipo tenga ciertas propiedades.

function getLength<T extends { length: number }>(item: T): number {
  return item.length;
}

getLength("hello"); // OK
getLength([1, 2, 3]); // OK
getLength(42); // Error: number no tiene length

Las restricciones hacen que tus genéricos sean más predecibles y previenen errores en tiempo de ejecución.

Clases genéricas

Las clases también pueden ser genéricas. Esto es útil para estructuras de datos como pilas, colas o repositorios.

class Stack<T> {
  private items: T[] = [];

  push(item: T): void {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }
}

const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
const top = numberStack.pop(); // number | undefined

Cuándo usar genéricos (y cuándo no)

Los genéricos son potentes, pero pueden hacer que el código sea más difícil de leer si se abusa de ellos. Aquí tienes una guía rápida:

Usa genéricos cuando Evita genéricos cuando
Necesitas preservar relaciones de tipo Un tipo unión simple o any es suficiente
Estás escribiendo utilidades reutilizables El tipo es siempre el mismo
Quieres evitar la duplicación de código Hace que el código sea más difícil de entender

Una buena regla general: si te encuentras escribiendo la misma lógica para múltiples tipos, considera los genéricos. Si estás añadiendo un tercer parámetro de tipo y nadie lo entiende, da un paso atrás.

Patrones comunes y mejores prácticas

Ejemplo del mundo real: un cliente API genérico

Vamos a unirlo todo con un ejemplo práctico: un cliente API simple que obtiene datos y devuelve una respuesta tipada.

async function fetchData<T>(url: string): Promise<T> {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  return response.json() as Promise<T>;
}

interface Post {
  userId: number;
  id: number;
  title: string;
  body: string;
}

async function main() {
  const post = await fetchData<Post>("https://jsonplaceholder.typicode.com/posts/1");
  console.log(post.title); // string
}

Este patrón es común en el código frontend. Obtienes seguridad de tipos sin repetirte para cada endpoint.

Preguntas frecuentes

¿Qué significa la sintaxis <T> en TypeScript?

<T> declara un parámetro de tipo. Es un marcador de posición para un tipo que se especificará cuando se use la función, interfaz o clase. Puedes nombrarlo como quieras, pero T es convencional para un solo tipo.

¿Puedo usar múltiples parámetros de tipo genérico?

Sí. Puedes definir tantos como necesites, separados por comas: <T, U, V>. Usa nombres descriptivos para mantener tu código legible.

¿Cómo restringo un genérico para que solo acepte objetos?

Usa T extends object. Esto asegura que T sea un tipo de objeto y no un primitivo como string o number. También puedes restringir a formas específicas, como T extends { id: number }.

Los genéricos son una parte fundamental de escribir TypeScript mantenible. Si estás trabajando con datos JSON y quieres validarlos o formatearlos rápidamente, prueba nuestro JSON Formatter para imprimir y inspeccionar tus respuestas de API.