Tema
Funciones tipadas
Las funciones son donde TypeScript más te ayuda: al tipar lo que entra y lo que sale, el editor te avisa si alguien llama tu función con datos equivocados o si olvidas devolver algo. En esta lección tiparás parámetros, retornos, callbacks, funciones asíncronas y hasta sobrecargas.
Concepto
Una función tipada es como un contrato de servicio: "si me das esto, te devuelvo aquello". Los parámetros son lo que el cliente debe entregar y el tipo de retorno es lo que la función promete regresar. TypeScript vigila ambos lados del contrato.
ts
// entrada salida
function sumar(a: number, b: number): number {
return a + b;
}
sumar(2, 3); // ✅ 5
sumar('2', 3); // ❌ Argument of type 'string' is not assignable to parameter of type 'number'.
sumar(2); // ❌ Expected 2 arguments, but got 1.Parámetros y retorno
ts
// Declaración de función
function calcularIva(subtotal: number, tasa: number): number {
return subtotal * tasa;
}
// Función flecha
const formatearPrecio = (monto: number, moneda: string): string =>
new Intl.NumberFormat('es-MX', { style: 'currency', currency: moneda }).format(monto);
// El retorno se puede inferir, pero anotarlo protege el contrato
function crearSlug(titulo: string) {
return titulo.toLowerCase().trim().replace(/\s+/g, '-');
} // TS infiere: string
// Si olvidas un camino de retorno, TS te avisa
function nivel(puntos: number): string {
if (puntos > 100) return 'oro';
if (puntos > 50) return 'plata';
// ❌ Function lacks ending return statement and return type
// does not include 'undefined'.
}Buena práctica
Anota siempre los parámetros (TS no puede inferirlos) y anota el retorno en funciones exportadas o importantes. Así, si cambias el cuerpo por error, el error aparece dentro de la función y no en los 20 lugares donde se usa.
Parámetros opcionales y por defecto
ts
// Opcional con ?: su tipo es string | undefined
function saludar(nombre: string, titulo?: string): string {
return titulo ? `Hola, ${titulo} ${nombre}` : `Hola, ${nombre}`;
}
saludar('García'); // 'Hola, García'
saludar('García', 'Dra.'); // 'Hola, Dra. García'
// Por defecto: TS infiere el tipo del valor
function aplicarDescuento(precio: number, porcentaje = 10): number {
return precio - precio * (porcentaje / 100);
}
aplicarDescuento(500); // 450
aplicarDescuento(500, 25); // 375
// Un objeto de opciones con valores por defecto (patrón muy común)
interface OpcionesToast {
mensaje: string;
tipo?: 'exito' | 'error' | 'info';
duracion?: number;
}
function mostrarToast({ mensaje, tipo = 'info', duracion = 3000 }: OpcionesToast): void {
console.log(`[${tipo}] ${mensaje} (${duracion} ms)`);
}
mostrarToast({ mensaje: 'Guardado', tipo: 'exito' });Error común
Los parámetros opcionales van después de los obligatorios. function f(a?: string, b: number) da error. Si necesitas muchos opcionales, usa un objeto de opciones como en mostrarToast.
Parámetros rest
ts
function sumarTodos(...numeros: number[]): number {
return numeros.reduce((a, b) => a + b, 0);
}
sumarTodos(1, 2, 3, 4); // 10
function registrar(nivel: 'info' | 'error', ...mensajes: string[]): void {
console.log(`[${nivel.toUpperCase()}]`, ...mensajes);
}
registrar('error', 'Falló el pago', 'pedido 1024');Tipos de función
Puedes describir la forma de una función como tipo, para reutilizarlo:
ts
// Un tipo que describe "función que recibe number y devuelve number"
type Operacion = (a: number, b: number) => number;
const multiplicar: Operacion = (a, b) => a * b; // a y b se infieren
const dividir: Operacion = (a, b) => a / b;
// En interfaces
interface Validador {
nombre: string;
validar: (valor: string) => boolean;
}
const validadorEmail: Validador = {
nombre: 'email',
validar: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v)
};Callbacks
Tipar los callbacks hace que el código que los recibe y el que los pasa estén de acuerdo:
ts
interface Producto {
nombre: string;
precio: number;
}
// La función recibe un callback que decide si un producto pasa el filtro
function filtrarProductos(
productos: Producto[],
criterio: (p: Producto) => boolean
): Producto[] {
return productos.filter(criterio);
}
const catalogo: Producto[] = [
{ nombre: 'Café', precio: 320 },
{ nombre: 'Taza', precio: 150 }
];
// p se infiere como Producto: tienes autocompletado
const baratos = filtrarProductos(catalogo, (p) => p.precio < 200);
// Callback que no devuelve nada: void
function alTerminar(callback: (error: Error | null, datos?: string) => void) {
setTimeout(() => callback(null, 'listo'), 1000);
}
alTerminar((error, datos) => {
if (error) return console.error(error.message);
console.log(datos);
});void en callbacks es permisivo
Un callback tipado como () => void sí puede devolver algo; simplemente se ignora. Por eso [1, 2].forEach((n) => lista.push(n)) funciona aunque push devuelva un número.
Funciones async y Promise
Una función async siempre devuelve una Promise. El tipo de retorno se escribe como Promise<Tipo>:
ts
interface Tarea {
id: number;
titulo: string;
completada: boolean;
}
async function obtenerTareas(): Promise<Tarea[]> {
const res = await fetch('https://jsonplaceholder.typicode.com/todos?_limit=5');
if (!res.ok) {
throw new Error(`Error HTTP ${res.status}`);
}
const datos = await res.json(); // res.json() devuelve Promise<any>
return datos.map((t: { id: number; title: string; completed: boolean }) => ({
id: t.id,
titulo: t.title,
completada: t.completed
}));
}
// Al usarla, TS sabe que tareas es Tarea[]
async function main() {
try {
const tareas = await obtenerTareas();
tareas.forEach((t) => console.log(t.titulo));
} catch (error) {
// En el catch, error es unknown: compruébalo
if (error instanceof Error) {
console.error(error.message);
}
}
}
// Una función async que no devuelve nada
async function guardar(t: Tarea): Promise<void> {
await fetch('/api/tareas', { method: 'POST', body: JSON.stringify(t) });
}Error común
Olvidar el await. Si escribes const tareas = obtenerTareas();, tareas es Promise<Tarea[]>, no Tarea[], y TS te marcará error al hacer tareas.forEach. ¡Es TypeScript salvándote de un bug clásico!
Sobrecargas
A veces una función acepta combinaciones distintas de parámetros y devuelve tipos distintos según el caso. Las sobrecargas declaran cada firma por separado:
ts
// Firmas públicas (lo que ve quien usa la función)
function obtenerElemento(id: number): HTMLElement | null;
function obtenerElemento(ids: number[]): HTMLElement[];
// Implementación (no visible desde fuera; debe cubrir todos los casos)
function obtenerElemento(entrada: number | number[]): HTMLElement | null | HTMLElement[] {
if (Array.isArray(entrada)) {
return entrada
.map((id) => document.getElementById(`item-${id}`))
.filter((el): el is HTMLElement => el !== null);
}
return document.getElementById(`item-${entrada}`);
}
const uno = obtenerElemento(5); // HTMLElement | null
const varios = obtenerElemento([1, 2]); // HTMLElement[]Úsalas con moderación: muchas veces una unión o un genérico (próxima lección) resuelven lo mismo de forma más simple.
Tipar this
En funciones normales puedes declarar qué es this con un primer parámetro "falso" llamado this. No existe al ejecutar; solo informa a TS:
ts
interface Boton {
etiqueta: string;
clicks: number;
}
function manejarClick(this: Boton): void {
this.clicks++;
console.log(`${this.etiqueta}: ${this.clicks} clicks`);
}
const boton: Boton = { etiqueta: 'Comprar', clicks: 0 };
manejarClick.call(boton); // ✅
manejarClick();
// ❌ The 'this' context of type 'void' is not assignable to method's 'this' of type 'Boton'.En la práctica moderna verás poco this fuera de clases; repasa el tema en POO y clases.
Ejercicios
- Escribe
calcularTotal(precio: number, cantidad: number, envio?: number): numberque sume el envío solo si se pasa. - Define
type Formateador = (valor: number) => stringy crea dos funciones con ese tipo: una que formatee como moneda y otra como porcentaje. - Escribe
reintentar(fn: () => Promise<string>, intentos: number): Promise<string>que ejecutefny, si falla, la vuelva a intentar hasta agotar los intentos. - Escribe
agruparPor(productos, obtenerClave)dondeobtenerClavees un callback que recibe unProductoy devuelve unstring; el resultado es unRecord<string, Producto[]>.
Ver solución
ts
// 1
function calcularTotal(precio: number, cantidad: number, envio?: number): number {
return precio * cantidad + (envio ?? 0);
}
// 2
type Formateador = (valor: number) => string;
const comoMoneda: Formateador = (v) =>
new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN' }).format(v);
const comoPorcentaje: Formateador = (v) => `${(v * 100).toFixed(1)}%`;
// 3
async function reintentar(fn: () => Promise<string>, intentos: number): Promise<string> {
let ultimoError: unknown;
for (let i = 0; i < intentos; i++) {
try {
return await fn();
} catch (error) {
ultimoError = error;
}
}
throw ultimoError;
}
// 4
interface Producto {
nombre: string;
categoria: string;
precio: number;
}
function agruparPor(
productos: Producto[],
obtenerClave: (p: Producto) => string
): Record<string, Producto[]> {
const grupos: Record<string, Producto[]> = {};
for (const p of productos) {
const clave = obtenerClave(p);
(grupos[clave] ??= []).push(p);
}
return grupos;
}
agruparPor(
[{ nombre: 'Café', categoria: 'bebidas', precio: 320 }],
(p) => p.categoria
);Resumen
- Tipa parámetros siempre; tipa el retorno en funciones exportadas.
?para parámetros opcionales (al final); valores por defecto se infieren....rest: Tipo[]para cantidad variable de argumentos.type Fn = (a: A) => Bdescribe funciones reutilizables, ideal para callbacks.- Las funciones
asyncdevuelvenPromise<Tipo>; encatch, el error esunknown. - Las sobrecargas declaran varias firmas; úsalas solo cuando una unión no basta.
thisse puede tipar con un primer parámetro especial.
Siguiente: Genéricos