CodeForge

TypeScript de Cero a Experto / Funciones e interfaces

Callbacks tipados y el tipado contextual

Teoría20 min20 XP

En JavaScript pasabas funciones a map, filter y a tus propias funciones de orden superior sin pensar en tipos. TypeScript los tipa —y, sorpresa, casi nunca tienes que anotarlos tú, gracias a una de sus magias más útiles: el tipado contextual. Hoy entiendes por qué el callback de un map ya sabe qué recibe.

El asistente que adivina por el contexto

Tipado contextual: no anotes los callbacks de métodos

Cuando usas map, filter, reduce, forEach sobre un array tipado, TS deduce el tipo de los parámetros del callback. Anotarlos es redundante:

contextual.ts
type Gasto = { nombre: string; valor: number };
const gastos: Gasto[] = [
{ nombre: "Café", valor: 4000 },
{ nombre: "Bus", valor: 2800 },
];

// NO anotes g: TS sabe que es Gasto por el contexto (gastos es Gasto[])
const valores = gastos.map((g) => g.valor);        // g: Gasto → valores: number[]
const caros = gastos.filter((g) => g.valor > 3000); // g: Gasto → caros: Gasto[]
const total = gastos.reduce((s, g) => s + g.valor, 0); // s: number, g: Gasto

// si escribieras g.balor, TS lo marcaría: conoce la forma de g sin que la anotes
// gastos.map((g) => g.balor);  // ❌ Property 'balor' does not exist on type 'Gasto'

Ese es el regalo: g es Gasto sin que lo escribas, y TS te protege igual de un typo. Anotar (g: Gasto) => ... funciona pero sobra —el contexto ya lo dijo. La regla de la inferencia (módulo 1) aplica de lleno a los callbacks: deja que TS haga el trabajo.

Tipar TUS funciones de orden superior

El tipado contextual funciona porque la función que RECIBE el callback declaró qué firma espera. Cuando escribes la tuya, ese tipo-función (lección anterior) es el que habilita la magia para quien la use:

hof-propia.ts
type Gasto = { nombre: string; valor: number };

// declaras que 'criterio' es una función Gasto → boolean:
function filtrarGastos(gastos: Gasto[], criterio: (g: Gasto) => boolean): Gasto[] {
return gastos.filter(criterio);
}

const gastos: Gasto[] = [
{ nombre: "Café", valor: 4000 },
{ nombre: "Bus", valor: 2800 },
];

// quien la usa NO anota g: tu firma le dio el contexto
const caros = filtrarGastos(gastos, (g) => g.valor > 3000);   // g: Gasto, inferido

Porque filtrarGastos declara criterio: (g: Gasto) => boolean, quien pase el callback recibe g: Gasto gratis. Tipar bien tus funciones de orden superior es regalarles a sus usuarios el mismo autocompletado y protección que te dan map y filter. Es cortesía de API.

La regla del void: callbacks perdonavidas

Un detalle sutil pero práctico. Si tu función espera un callback que devuelve void, TS acepta un callback que SÍ devuelve algo —simplemente ignora ese valor:

void-callback.ts
// forEach espera un callback (elemento) => void
const numeros = [1, 2, 3];
const acumulado: number[] = [];

// esto compila aunque push DEVUELVE un number (el nuevo length):
numeros.forEach((n) => acumulado.push(n));
// TS no se queja: un callback 'void' puede devolver algo, se descarta

// ¿por qué? porque quien llama a forEach NO usa el retorno del callback.
// esta flexibilidad evita errores molestos sin perder seguridad real.

Ese agruparPor usa un genérico <T> (lo dominas en el módulo 5), pero fíjate en lo que importa hoy: quien lo llama escribe (g) => g.categoria sin anotar g, y TS lo infiere como Gasto. Tu función bien tipada le da esa comodidad.

Escribes const nombres = usuarios.map(u => u.nombre) donde usuarios es Usuario[]. ¿Necesitas anotar (u: Usuario)?

Mini-reto

Aprovecha el contexto: 1) escribe function ordenarPor<T>(items: T[], clave: (x: T) => number): T[] que devuelva una copia ordenada por el número que devuelve clave (usa toSorted o [...items].sort); 2) úsala sobre un array de gastos pasando (g) => g.valor SIN anotar g; 3) confirma en tu editor que g se infiere como el tipo del gasto. Sientes cómo tu función bien tipada le regala el contexto a quien la usa.

Qué sigue

Has tipado objetos con type. Existe otra herramienta para lo mismo con superpoderes propios: interface. La próxima lección la presenta —contratos de objetos que se extienden y se combinan— antes de la gran pregunta que zanjaremos después: ¿interface o type?