TypeScript de Cero a Experto / Genéricos
Genéricos en clases y valores por defecto
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:
// 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 numberPila<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:
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: 1Un 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:
// 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 = unknownDeclaras 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.