En esta página
Tipado de funciones
Tipado de funciones
Las funciones son el elemento central de cualquier programa TypeScript. Tipar correctamente las funciones significa ser preciso sobre qué aceptan y qué prometen devolver, habilitando autocompletado exacto y detección de errores en el punto de uso. Esta lección cubre todos los aspectos del tipado de funciones: expresiones de tipo, call signatures, sobrecargas, parámetros especiales y el parámetro this.
Expresiones de tipo función
La forma más directa de representar una función como tipo es con una expresión de tipo función:
// Tipo función inline
type Comparador = (a: number, b: number) => number;
// Tipo función con nombre explícito
type Transformador<T, U> = (entrada: T) => U;
type Predicado<T> = (elemento: T) => boolean;
// Uso en variables
const ordenarAscendente: Comparador = (a, b) => a - b;
const ordenarDescendente: Comparador = (a, b) => b - a;
// Uso en parámetros
function ordenar<T>(arreglo: T[], comparador: Comparador): T[] {
return [...arreglo].sort(comparador);
}Expresiones vs declaraciones
// Declaración de función: hoisted, más legible para funciones nombradas
function calcular(a: number, b: number): number {
return a + b;
}
// Expresión de función: asignable a variables, ideal para callbacks
const calcular2 = (a: number, b: number): number => a + b;
// Expresión con tipo explícito en la variable (útil para documentar)
const calcular3: (a: number, b: number) => number = (a, b) => a + b;Call signatures en interfaces
Cuando una función tiene propiedades adicionales (que puede tener en JavaScript), se usa una call signature dentro de una interfaz:
interface FunciónConMetadatos {
(entrada: string): string; // Call signature
descripción: string; // Propiedad adicional
versión: number;
}
const transformar: FunciónConMetadatos = (s: string) => s.toUpperCase();
transformar.descripción = 'Transforma texto a mayúsculas';
transformar.versión = 1;
console.log(transformar('hola')); // 'HOLA'
console.log(transformar.descripción); // 'Transforma texto a mayúsculas'Call signatures vs expresiones de tipo
// Expresión de tipo: función sin propiedades adicionales
type Fn1 = (x: number) => string;
// Call signature en interface: función que puede tener propiedades
interface Fn2 {
(x: number): string;
}
// Diferencia: puedes añadir propiedades a Fn2, no a Fn1Parámetros opcionales y por defecto
// Parámetro opcional: puede no pasarse (tipo: T | undefined internamente)
function saludar(nombre: string, saludo?: string): string {
return `${saludo ?? 'Hola'}, ${nombre}!`;
}
saludar('Ana'); // 'Hola, Ana!'
saludar('Ana', 'Buenos días'); // 'Buenos días, Ana!'
// Parámetro con valor por defecto: el tipo se infiere del default
function crearUsuario(nombre: string, rol: 'admin' | 'lector' = 'lector') {
return { nombre, rol };
}
crearUsuario('Ana'); // { nombre: 'Ana', rol: 'lector' }
crearUsuario('Carlos', 'admin'); // { nombre: 'Carlos', rol: 'admin' }Importante: los parámetros opcionales deben estar al final. Los parámetros con valor por defecto pueden estar en cualquier posición pero es más claro tenerlos al final también.
Parámetros rest tipados
// Rest con tipo primitivo
function unir(separador: string, ...palabras: string[]): string {
return palabras.join(separador);
}
unir(', ', 'manzana', 'banana', 'cereza'); // 'manzana, banana, cereza'
// Rest con tipo complejo
function registrarEventos(...eventos: Array<{ tipo: string; timestamp: Date }>): void {
eventos.forEach(e => console.log(`[${e.timestamp.toISOString()}] ${e.tipo}`));
}
// Spread de tupla como argumentos
type ArgsConsola = [mensaje: string, ...datos: unknown[]];
function registrar(...args: ArgsConsola): void {
const [mensaje, ...datos] = args;
console.log(mensaje, ...datos);
}Tipado de callbacks
Los callbacks son uno de los puntos más críticos del tipado de funciones. Un callback mal tipado puede propagar unknown o any por todo el código.
// Callback simple
function procesarArreglo<T>(
items: T[],
callback: (item: T, índice: number) => void
): void {
items.forEach(callback);
}
// Callback que retorna un valor
function mapearArreglo<T, U>(
items: T[],
transformar: (item: T) => U
): U[] {
return items.map(transformar);
}
// Callback asíncrono
async function procesarLote<T>(
items: T[],
procesador: (item: T) => Promise<void>,
concurrencia = 5
): Promise<void> {
for (let i = 0; i < items.length; i += concurrencia) {
const lote = items.slice(i, i + concurrencia);
await Promise.all(lote.map(procesador));
}
}
// Uso tipado:
procesarArreglo([1, 2, 3], (num, idx) => {
console.log(`${idx}: ${num}`); // num es number, idx es number
});Tipo `void` en callbacks: comportamiento especial
Cuando un callback tiene tipo de retorno void, el llamado puede retornar cualquier valor; TypeScript simplemente lo descarta. Esto es diferente al uso de void en funciones normales.
type CallbackVoid = () => void;
// Esto es válido — el valor retornado se ignora
const fn: CallbackVoid = () => 'hola'; // ✅
const fn2: CallbackVoid = () => 42; // ✅
// Útil para APIs como addEventListener:
const manejador: CallbackVoid = () => { /* sin return */ };
document.addEventListener('click', manejador); // addEventListener espera () => voidSobrecargas de función
Las sobrecargas permiten definir múltiples firmas para la misma función. TypeScript verifica el tipo correcto según los argumentos en el punto de llamada.
// Firmas de sobrecarga (sin implementación)
function crear(tipo: 'usuario', nombre: string, email: string): { tipo: 'usuario'; nombre: string; email: string };
function crear(tipo: 'producto', nombre: string, precio: number): { tipo: 'producto'; nombre: string; precio: number };
// Implementación (debe ser compatible con todas las sobrecargas)
function crear(
tipo: 'usuario' | 'producto',
nombre: string,
tercero: string | number
): { tipo: 'usuario'; nombre: string; email: string } | { tipo: 'producto'; nombre: string; precio: number } {
if (tipo === 'usuario') {
return { tipo: 'usuario', nombre, email: tercero as string };
}
return { tipo: 'producto', nombre, precio: tercero as number };
}
// TypeScript infiere el tipo de retorno correcto según la sobrecarga:
const usuario = crear('usuario', 'Ana', '[email protected]');
usuario.email; // ✅ TypeScript sabe que este campo existe
const producto = crear('producto', 'Laptop', 999);
producto.precio; // ✅ TypeScript sabe que este campo existeUn ejemplo más práctico: la función obtener que retorna tipos distintos según el parámetro:
function obtener(clave: 'nombre'): string;
function obtener(clave: 'edad'): number;
function obtener(clave: 'activo'): boolean;
function obtener(clave: 'nombre' | 'edad' | 'activo'): string | number | boolean {
const datos = { nombre: 'Ana', edad: 28, activo: true };
return datos[clave];
}
const nombre = obtener('nombre'); // tipo: string
const edad = obtener('edad'); // tipo: number
const activo = obtener('activo'); // tipo: booleanReglas de las sobrecargas
- Las firmas de sobrecarga no tienen cuerpo.
- La implementación tiene su propia firma más general (no visible externamente).
- La firma de implementación debe ser compatible con todas las sobrecargas.
- TypeScript usa las sobrecargas en orden (la primera que encaje gana).
El parámetro `this`
JavaScript tiene semántica compleja para this. TypeScript te permite anotar el tipo de this esperado como el primer parámetro (que no es un parámetro real, solo información de tipos):
interface Botón {
texto: string;
deshabilitado: boolean;
onClick(this: Botón): void;
}
const botón: Botón = {
texto: 'Enviar',
deshabilitado: false,
onClick(this: Botón) {
if (this.deshabilitado) return;
console.log(`Clic en: ${this.texto}`);
},
};
botón.onClick(); // ✅
const fn = botón.onClick;
fn(); // ❌ Error: 'this' context is not of type 'Botón'Esto es especialmente útil cuando compartes métodos entre clases o cuando trabajas con APIs que llaman callbacks con this específicos.
Funciones como parámetros de otras funciones
Los patrones de orden superior son naturales en TypeScript:
// Función que retorna una función (currying manual)
function multiplicarPor(factor: number): (n: number) => number {
return (n: number) => n * factor;
}
const doble = multiplicarPor(2);
const triple = multiplicarPor(3);
console.log(doble(5)); // 10
console.log(triple(4)); // 12
// Composición de funciones
function componer<A, B, C>(
f: (b: B) => C,
g: (a: A) => B
): (a: A) => C {
return (a: A) => f(g(a));
}
const normalizarTexto = componer(
(s: string) => s.trim(),
(s: string) => s.toLowerCase()
);
console.log(normalizarTexto(' HOLA MUNDO ')); // 'hola mundo'Funciones asíncronas
Las funciones async siempre retornan una Promise. TypeScript infiere el tipo genérico de la promesa del tipo de retorno de la función:
// El tipo de retorno es Promise<Usuario>
async function obtenerUsuario(id: string): Promise<Usuario> {
const respuesta = await fetch(`/api/usuarios/${id}`);
if (!respuesta.ok) {
throw new Error(`Usuario ${id} no encontrado`);
}
return respuesta.json() as Promise<Usuario>;
}
// Funciones que pueden fallar: retornar Resultado en lugar de lanzar
async function obtenerUsuarioSeguro(id: string): Promise<Resultado<Usuario, string>> {
try {
const usuario = await obtenerUsuario(id);
return { ok: true, valor: usuario };
} catch (err) {
const mensaje = err instanceof Error ? err.message : 'Error desconocido';
return { ok: false, error: mensaje };
}
}Con el sistema de tipado de funciones dominado, estás listo para dar el siguiente paso: los genéricos. Son el mecanismo que permite reutilizar toda esta lógica tipada con cualquier tipo de datos.
Inicia sesión para guardar tu progreso