Las funciones en TypeScript son ciudadanos de primera clase (First-Class Citizens). Pueden pasarse como argumentos, devolverse desde otras funciones y asignarse a variables.
El tipado de una función representa un contrato matemático formal:
f: (A1, A2, ...An) -> R
El contrato define un conjunto de parámetros de entrada y la garantía del tipo de valor de retorno.
A diferencia de muchos lenguajes de tipado estático, las funciones en TypeScript poseen propiedades únicas:
strictFunctionTypes está activada.void vs undefined vs never): distinción entre funciones sin un resultado útil, el retorno de undefined y funciones que nunca devuelven el control (bucles infinitos, lanzamiento de excepciones).Veamos las diferencias entre void, undefined y never:
+-------------------------------------------------------------------------+
| SEMÁNTICA DE RETORNO: VOID vs UNDEFINED vs NEVER |
+-------------------------------------------------------------------------+
| void: |
| "No me importa el resultado". La función realiza un efecto secundario.|
| En JS devolverá undefined, pero TypeScript permite que los callbacks |
| devuelvan cualquier valor, ignorándolo por parte del llamador. |
+-------------------------------------------------------------------------+
| undefined: |
| "La función devuelve explícitamente el valor undefined". |
| Es obligatorio return undefined o un return vacío. |
+-------------------------------------------------------------------------+
| never: |
| "La función NUNCA terminará normalmente". |
| O bien lanza un error throw new Error(), o se queda en un bucle while.|
+-------------------------------------------------------------------------+La función que llama puede pasar menos argumentos de los declarados, pero no más de los requeridos (verificación de aridad).
Cada parámetro de la función objetivo debe aceptar de forma segura el tipo del argumento correspondiente de la función original.
El tipo de resultado de la función original debe ser un subtipo del tipo de retorno declarado de la firma objetivo.
El compilador compara los argumentos de la llamada con las sobrecargas de arriba a abajo hasta la primera coincidencia completa.
¿Por qué el compilador permite que el código `const fn: () => void = () => 42;` pase la verificación de tipos con éxito, a pesar de que la función flecha devuelve un número?
(event: React.MouseEvent<HTMLButtonElement>) => void.async function fetchEntity(): Promise<Entity> para protegerse contra un Promise<unknown> implícito.readFile(path, 'utf-8'): string vs readFile(path): Buffer).La firma de una función se puede declarar como un tipo de flecha o dentro del cuerpo de una interfaz:
// 1. Alias de tipo de flecha:
type FormValidator = (value: string, minLength?: number) => boolean;
// 2. Tipo de objeto con firma de llamada (Call Signature):
interface Logger {
// Firma de llamada
(message: string, level?: 'info' | 'warn' | 'error'): void;
// Propiedad estática adjunta a la función
defaultLevel: 'info' | 'warn' | 'error';
}
const consoleLogger: Logger = Object.assign(
(msg: string, level: 'info' | 'warn' | 'error' = 'info') => {
console.log(`[${level.toUpperCase()}] ${msg}`);
},
{ defaultLevel: 'info' as const }
);Una sobrecarga consta de uno o más encabezados de sobrecarga (sin cuerpo) y una firma de implementación (con cuerpo):
// Sobrecargas públicas (visibles para los consumidores de la API)
function formatTimestamp(timestamp: number): string;
function formatTimestamp(date: Date, locale: string): string;
// Firma de implementación (¡NO visible directamente para los consumidores!)
function formatTimestamp(arg1: number | Date, arg2?: string): string {
if (typeof arg1 === 'number') {
return new Date(arg1).toISOString();
}
return arg1.toLocaleDateString(arg2 ?? 'en-US');
}
// Llamadas correctas:
formatTimestamp(1728000000000); // OK
formatTimestamp(new Date(), 'ru-RU'); // OK
// ERROR: ¡el compilador solo compara con las sobrecargas públicas, no con la implementación!
// formatTimestamp(new Date()); // TS2554: Expected 2 arguments, but got 1.Los parámetros opcionales (param?: Type) deben seguir estrictamente a los parámetros obligatorios:
// Los parámetros por defecto hacen que el parámetro sea opcional automáticamente
function createRequest(
url: string,
timeoutMs: number = 5000,
headers?: Record<string, string>
) {
return { url, timeoutMs, headers: headers ?? {} };
}
// Parámetros Rest tipados usando tuplas:
type LogArgs = [code: number, message: string, ...details: unknown[]];
function emitAuditLog(...args: LogArgs): void {
const [code, message, ...details] = args;
console.log(`Audit #${code}: ${message}`, details);
}
emitAuditLog(403, 'Forbidden', { ip: '127.0.0.1' }); // Correcto¿Por qué el código que llama no puede invocar una función con argumentos que coincidan con la firma de implementación, si esa combinación no existe en las sobrecargas públicas?
TypeScript compara las sobrecargas de arriba a abajo. Si se coloca una firma más amplia primero, interceptará todas las llamadas:
// ERROR: ¡la primera sobrecarga bloqueará la segunda!
function get(url: string): unknown;
function get(url: 'https://api.wpskills.dev'): SpecificResponse;
// CORRECTO: ¡coloque las firmas específicas PRIMERO y las amplias al final!
function get(url: 'https://api.wpskills.dev'): SpecificResponse;
function get(url: string): unknown;Declaración de una función con never si termina de forma normal:
// ERROR: TS2534: A function returning 'never' cannot have a reachable end point.
function logError(msg: string): never {
console.error(msg);
// ¡No hay throw o while(true)!
}Use void para funciones que realizan un efecto secundario.