certmundo.
es‑mx

6 min de lectura

¿Qué son los genéricos en TypeScript y cómo usarlos?

Los genéricos son una herramienta de TypeScript que permite escribir funciones, clases e interfaces que trabajan con cualquier tipo sin perder la seguridad de tipado.

En lugar de repetir el mismo código para string, number u otros tipos, escribes una sola versión flexible. TypeScript deduce el tipo correcto en cada uso.


¿Por qué existen los genéricos?

Sin genéricos, tienes dos opciones malas: repetir código o usar any.

Usar any desactiva la verificación de tipos. Eso borra la ventaja principal de TypeScript.

Los genéricos resuelven esto: código flexible y seguro al mismo tiempo.


Sintaxis básica de un genérico

La sintaxis usa corchetes angulares <T> después del nombre de la función o clase.

function identidad<T>(valor: T): T {
  return valor;
}

La letra T es un parámetro de tipo. Funciona como una variable, pero para tipos.

Puedes llamarla T, U, V, o cualquier nombre descriptivo como TProducto.


Ejemplos con funciones genéricas

Ejemplo 1 — Función simple

function identidad<T>(valor: T): T {
  return valor;
}

const nombre = identidad<string>("Bimbo");
const precio = identidad<number>(18500);

console.log(nombre);  // Bimbo
console.log(precio);  // 18500

TypeScript sabe que nombre es string y precio es number. No necesitas anotarlos manualmente.

Ejemplo 2 — Función con arreglo

Esta función obtiene el primer elemento de cualquier arreglo.

function primero<T>(lista: T[]): T {
  return lista[0];
}

const productos = ["Pan Bimbo", "Marinela", "Ricolino"];
const precios = [12500, 18500, 9800];

console.log(primero(productos)); // Pan Bimbo
console.log(primero(precios));   // 12,500

La misma función trabaja con string[] y con number[]. TypeScript infiere el tipo automáticamente.

Ejemplo 3 — Función con dos parámetros de tipo

Puedes usar más de un parámetro de tipo. Aquí construimos un par clave-valor.

function crearPar<K, V>(clave: K, valor: V): [K, V] {
  return [clave, valor];
}

const parProducto = crearPar<string, number>("Precio OXXO", 28500);
console.log(parProducto); // ["Precio OXXO", 28500]

const parActivo = crearPar<string, boolean>("Cuenta activa", true);
console.log(parActivo); // ["Cuenta activa", true]

K y V son convenciones para "Key" y "Value". Son solo nombres; puedes elegir otros.


Genéricos con interfaces

Puedes aplicar genéricos a interfaces para describir estructuras de datos flexibles.

interface Respuesta<T> {
  exito: boolean;
  datos: T;
  mensaje: string;
}

Esta interface representa cualquier respuesta de una API. El campo datos puede ser cualquier tipo.

interface Producto {
  id: number;
  nombre: string;
  precio: number;
}

const respuestaProducto: Respuesta<Producto> = {
  exito: true,
  datos: { id: 1, nombre: "Despensa Bimbo", precio: 12500 },
  mensaje: "Producto encontrado"
};

const respuestaLista: Respuesta<string[]> = {
  exito: true,
  datos: ["FEMSA", "Liverpool", "Bimbo"],
  mensaje: "Lista de empresas"
};

console.log(respuestaProducto.datos.nombre); // Despensa Bimbo
console.log(respuestaLista.datos[1]);         // Liverpool

Reutilizas Respuesta<T> para cualquier tipo de dato que regrese tu API.


Genéricos con clases

Las clases también aceptan parámetros de tipo. Esto es útil para estructuras como pilas, colas o repositorios.

Ejemplo — Clase Almacén genérico

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

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

  obtener(indice: number): T {
    return this.items[indice];
  }

  total(): number {
    return this.items.length;
  }
}

Ahora usamos la clase con tipos concretos:

const almacenProductos = new Almacen<string>();
almacenProductos.agregar("Tequila FEMSA");
almacenProductos.agregar("Cerveza Sol");

console.log(almacenProductos.obtener(0)); // Tequila FEMSA
console.log(almacenProductos.total());    // 2

const almacenPrecios = new Almacen<number>();
almacenPrecios.agregar(18500);
almacenPrecios.agregar(9800);

console.log(almacenPrecios.obtener(1)); // 9,800

TypeScript garantiza que almacenProductos solo acepta string y almacenPrecios solo acepta number.


Restricciones de tipo con extends

A veces necesitas limitar qué tipos puede recibir un genérico. Usas la palabra clave extends.

interface ConNombre {
  nombre: string;
}

function mostrarNombre<T extends ConNombre>(item: T): string {
  return item.nombre;
}

Esta función solo acepta tipos que tengan la propiedad nombre.

const empresa = { nombre: "Mercado Libre", pais: "México" };
const empleado = { nombre: "Carlos", puesto: "Dev", salario: 28000 };

console.log(mostrarNombre(empresa));   // Mercado Libre
console.log(mostrarNombre(empleado));  // Carlos

// Esto causa error en tiempo de compilación:
// mostrarNombre(12500); ❌ number no tiene 'nombre'

Las restricciones hacen los genéricos más seguros sin quitarles flexibilidad.


Valor por defecto en genéricos

Desde TypeScript 2.3, puedes asignar un tipo por defecto a un parámetro genérico.

interface Contenedor<T = string> {
  valor: T;
}

const a: Contenedor = { valor: "OXXO" };       // T es string por defecto
const b: Contenedor<number> = { valor: 4500 }; // T es number explícito

Esto reduce la verbosidad cuando el tipo más común ya está definido.


Errores comunes

Error 1 — Usar any en lugar de un genérico

// ❌ Incorrecto: pierdes el tipado
function obtenerValor(lista: any[]): any {
  return lista[0];
}

// ✅ Correcto: mantienes el tipado
function obtenerValor<T>(lista: T[]): T {
  return lista[0];
}

Con any, TypeScript no detecta errores. Con <T>, sí.

Error 2 — Asumir propiedades que el tipo genérico no tiene

// ❌ Incorrecto: T puede no tener 'precio'
function descuento<T>(item: T): number {
  return item.precio * 0.9; // Error: Property 'precio' does not exist on type 'T'
}

// ✅ Correcto: restringe T para garantizar la propiedad
interface ConPrecio {
  precio: number;
}

function descuento<T extends ConPrecio>(item: T): number {
  return item.precio * 0.9;
}

Siempre usa extends cuando necesites acceder a propiedades específicas del tipo.

Error 3 — Olvidar el parámetro de tipo en clases

// ❌ Sin tipo, TypeScript infiere 'unknown' o lanza error
const caja = new Almacen();
caja.agregar("Producto"); // Puede fallar según la configuración

// ✅ Especifica el tipo al crear la instancia
const caja = new Almacen<string>();
caja.agregar("Producto");

Siempre indica el tipo cuando creas instancias de clases genéricas.


Tabla de referencia rápida

Sintaxis Descripción Ejemplo
function f<T>(x: T): T Función genérica básica identidad<string>("Bimbo")
<T, U> Múltiples parámetros de tipo crearPar<string, number>(...)
interface I<T> Interface genérica Respuesta<Producto>
class C<T> Clase genérica Almacen<string>
T extends X Restricción de tipo T extends ConNombre
T = string Tipo por defecto Contenedor<T = string>

Cuándo usar genéricos

Usa genéricos cuando tu función, clase o interface trabaja con distintos tipos pero con la misma lógica.

No uses genéricos si la función siempre recibe el mismo tipo. En ese caso, anota el tipo directamente.

La regla práctica: si copiarías y pegarías código solo para cambiar el tipo, usa un genérico.

Puntos clave

  • Un genérico usa la sintaxis `<T>` para crear funciones, clases e interfaces que trabajan con cualquier tipo sin perder la verificación de TypeScript.
  • TypeScript puede inferir el parámetro de tipo automáticamente en muchos casos; no siempre es necesario escribir `<string>` o `<number>` de forma explícita.
  • Usa `extends` para restringir un genérico y garantizar que el tipo recibido tenga las propiedades que necesitas, como `T extends ConNombre`.
  • Las interfaces genéricas como `Respuesta<T>` son ideales para modelar respuestas de API que devuelven distintos tipos de datos según el endpoint.
  • Evita `any` como sustituto de genéricos: `any` desactiva el tipado, mientras que `<T>` mantiene la seguridad y la flexibilidad al mismo tiempo.

Comparte esta lección: