CodeForge

TypeScript de Cero a Experto / Genéricos

Genéricos en clases y valores por defecto

Teoría20 min20 XP

El tercer hogar de los genéricos: las CLASES. Con ellas construyes estructuras de datos reutilizables y tipadas —una pila que guarda cualquier cosa, un almacén observable— sin reescribir la lógica por cada tipo. Y cierras el tema con los valores por defecto de los genéricos, un detalle que hace tus tipos cómodos de usar.

Un molde de estructuras

Una clase genérica: Pila<T>

El <T> va junto al nombre de la clase, y se usa en sus campos y métodos:

pila.ts
// una pila (LIFO) que guarda elementos de tipo T, sea cual sea
class Pila<T> {
private items: T[] = [];   // el T fluye a los campos

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

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

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

// se instancia tipada: cada pila sabe qué guarda
const numeros = new Pila<number>();
numeros.apilar(1);
numeros.apilar(2);
const ultimo = numeros.desapilar();   // number | undefined

const textos = new Pila<string>();
textos.apilar("hola");
// textos.apilar(42);   // ❌ es una Pila<string>, no acepta number

Pila<T> escribe la lógica de apilar/desapilar UNA vez, y Pila<number> / Pila<string> la especializan. Dentro de la clase, T es el tipo de los elementos: items es T[], apilar recibe T, desapilar devuelve T | undefined. La misma estructura, tipada para lo que sea —sin duplicar código ni recurrir a any.

Un caso real: Store<T> observable

Recuerda el store observable del módulo 13 de JavaScript —ahora tipado y genérico, sirve para cualquier forma de estado:

store.ts
type Escucha<T> = (estado: T) => void;

class Store<T> {
private escuchas: Escucha<T>[] = [];

constructor(private estado: T) {}   // el estado inicial define T

obtener(): T {
  return this.estado;
}

actualizar(nuevo: T): void {
  this.estado = nuevo;
  for (const escucha of this.escuchas) escucha(this.estado);
}

suscribir(escucha: Escucha<T>): void {
  this.escuchas.push(escucha);
}
}

// TS infiere T del estado inicial:
const contador = new Store(0);          // Store<number>
contador.suscribir((n) => console.log("contador:", n));
contador.actualizar(1);                 // contador: 1

Un solo Store<T> sirve para un contador (Store<number>), un carrito (Store<Producto[]>) o un formulario (Store<Datos>). Es, en esencia, cómo están tipadas por dentro las librerías de estado como Zustand (que usaste sin tipos en JS). Los genéricos son lo que hace posible una librería que funciona con TU forma de estado, sin conocerla de antemano.

Valores por defecto de los genéricos

Como los parámetros de una función, los parámetros de tipo pueden tener un valor por defecto —para no obligar a especificarlos en el caso común:

defaults.ts
// T = string: si no especificas el tipo, asume string
class Coleccion<T = string> {
private items: T[] = [];
agregar(item: T) { this.items.push(item); }
todos(): T[] { return this.items; }
}

const textos = new Coleccion();          // Coleccion<string> por defecto
textos.agregar("hola");

const numeros = new Coleccion<number>(); // especificas otro tipo cuando lo necesitas
numeros.agregar(42);

// también en tipos/interfaces:
type Respuesta<T = unknown> = { ok: boolean; datos: T };
const generica: Respuesta = { ok: true, datos: "lo que sea" };  // T = unknown

Declaras class Caja<T = string>. Si escribes const c = new Caja() sin especificar tipo, ¿qué es T?

Mini-reto

Construye una estructura tuya: 1) escribe class ParClaveValor<K, V> con métodos set(clave: K, valor: V), get(clave: K): V | undefined y tiene(clave: K): boolean, usando un array de pares por dentro; 2) instánciala como ParClaveValor<string, number> y guarda un par de gastos; 3) confirma que TS te protege de pasar tipos equivocados a set. Acabas de construir un mini-Map tipado —la estructura genérica por excelencia.

Qué sigue

Cierras el módulo con un reto que junta todo: una estructura de datos genérica, constraints y un Resultado<T> trabajando en equipo. Luego, el módulo 6 revela un regalo enorme: los UTILITY TYPES —transformadores de tipos que TypeScript ya trae hechos (Partial, Pick, Omit, Record…), construidos con la maquinaria genérica que acabas de dominar.