TypeScript de Cero a Experto / Funciones e interfaces
Callbacks tipados y el tipado contextual
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:
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:
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, inferidoPorque 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:
// 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?