Menu

Tipos de función en TypeScript: sintaxis flecha y callbacks

Cómo escribir el tipo de una función en TypeScript: la sintaxis flecha (a: number) => string, alias de tipo para funciones, tipar funciones flecha y callbacks, firmas de llamada y de construcción, y por qué el tipo Function es demasiado laxo.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Un tipo de función describe los parámetros y el valor de retorno de una función, y se escribe con una flecha: (a: number, b: number) => number. Ponle nombre con type y podrás usarlo en variables, parámetros y propiedades de objetos.

La función asignada a add no necesita anotaciones: los tipos de sus parámetros vienen de Operation. Esto se llama tipado contextual, y es la razón principal para poner nombre a los tipos de función.

Sintaxis de los tipos de función

La forma es (parameters) => ReturnType. Cada parámetro necesita un nombre y un tipo. Los nombres están para facilitar la lectura y las pistas del editor; encaja cualquier función con tipos de parámetros compatibles, los llame como los llame.

type Predicate = (value: number) => boolean;
type Formatter = (value: number, digits?: number) => string; // optional parameter
type Logger = (...parts: string[]) => void;                    // rest parameter
type Factory = () => { id: number };                           // no parameters

El nombre no es opcional. (string) => void declara un parámetro llamado string sin tipo, y el modo strict lo rechaza con TS7051: Parameter has a name but no type. Did you mean 'arg0: string'?. Escribe (value: string) => void.

Tipar funciones flecha

Una función flecha se puede tipar de dos formas. Anota la propia función, o anota la variable con un tipo de función y deja que se infieran los parámetros.

La forma 1 es la habitual para una función independiente. La forma 3 compensa cuando varias funciones comparten una firma (manejadores, comparadores, conversores), porque la firma se escribe una sola vez y cualquier cambio en ella se comprueba en todas partes.

Tipos de callback

Un parámetro cuyo tipo es un tipo de función es un callback. La función de quien llama se comprueba contra él, y sus parámetros se infieren de él.

Dos reglas hacen que los callbacks sean cómodos de usar:

  • Recibir menos parámetros está bien. Una función que recibe un parámetro se puede pasar donde se ofrecen dos. JavaScript ignora los argumentos de más, así que TypeScript lo permite. Recibir más parámetros de los que ofrece el tipo es un error (Target signature provides too few arguments.).
  • Un retorno void acepta cualquier cosa. Un callback tipado (...) => void puede devolver un valor; quien llama promete no usarlo. Por eso list.forEach((x) => other.push(x)) compila aunque push devuelva un número.

Los tipos de los parámetros tienen que ser compatibles de todos modos. Pasar (x: string) => ... donde se espera (a: number, b: number) => number falla con Types of parameters 'x' and 'a' are incompatible.

Firmas de llamada

(n: number) => string es una forma abreviada de un tipo objeto con una firma de llamada: { (n: number): string }. Necesitas la forma larga cuando la función además lleva propiedades.

TypeScript te permite añadir propiedades a una función declarada con const en el mismo ámbito, y las registra en el tipo de la función. Una interfaz también puede contener una firma de llamada: interface Counter { (): number; count: number }.

Una firma de construcción describe algo que se llama con new. Añade new delante: new (name: string) => User, o { new (name: string): User } en forma de objeto. Así se tipa un parámetro que recibe una clase.

Sintaxis de método frente a sintaxis de propiedad

En un tipo objeto, un miembro función se puede escribir como método, handle(value: string): void, o como una propiedad que contiene una función, handle: (value: string) => void. Parecen intercambiables, pero con strict se comprueban de forma distinta:

El compilador informa:

index.ts(8,27): error TS2322: Type '(value: string) => void' is not assignable to type '(value: string | number) => void'.
  Types of parameters 'value' and 'value' are incompatible.
    Type 'string | number' is not assignable to type 'string'.
      Type 'number' is not assignable to type 'string'.

El error tiene razón: onlyStrings no puede manejar un número. La forma de método deja pasar el mismo fallo (los parámetros de los métodos se comprueban de forma «bivariante» por razones históricas), y a.handle(42) fallaría en tiempo de ejecución con TypeError: value.toUpperCase is not a function. Borra la línea de b para ver ese fallo. Cuando escribas tus propios tipos objeto, la forma de propiedad detecta más errores.

Evita el tipo Function

Function es el tipo integrado que cumple cualquier valor función. Casi nunca es lo que quieres: TypeScript no conoce los parámetros ni el tipo de retorno, así que se acepta cualquier llamada y devuelve any.

Sustituye Function por la firma real. Cuando de verdad aceptas cualquier función (un debounce genérico, un envoltorio para logs), (...args: never[]) => unknown acepta todas las funciones y mantiene el resultado como unknown en lugar de any. Para funciones cuyos tipos dependen de su entrada, consulta genéricos: un tipo de función genérico se ve así: <T>(value: T) => T.

Preguntas frecuentes

¿Cómo se define un tipo de función en TypeScript?

Usa la sintaxis flecha: (a: number, b: number) => number. Ponle nombre con un alias de tipo, type Compare = (a: number, b: number) => number;, y usa ese nombre en variables, parámetros y propiedades. Los nombres de los parámetros forman parte de la sintaxis pero solo sirven de documentación: una función con otros nombres de parámetro también encaja.

¿Cómo tipo un parámetro callback en TypeScript?

Escribe el tipo de función como tipo del parámetro: function onEach(items: string[], cb: (item: string, index: number) => void) { ... }. Quien llama puede pasar una función que reciba menos parámetros, y los parámetros del propio callback se infieren de ese tipo, así que onEach(list, (item) => ...) no necesita anotación.

¿Por qué no debo usar el tipo Function en TypeScript?

Function acepta cualquier función y llamarla no se comprueba: se permite cualquier argumento y el resultado es any. Escribe la firma real, como () => void o (value: string) => number, o (...args: never[]) => unknown cuando de verdad aceptas cualquier función.

¿Qué diferencia hay entre un tipo de función y una firma de llamada?

Describen lo mismo. (n: number) => string es una forma abreviada del tipo objeto { (n: number): string }. La forma con firma de llamada hace falta cuando la función además tiene propiedades, por ejemplo { (n: number): string; label: string }.

¿Cómo tipo una función flecha en TypeScript?

O bien anotas sus parámetros y su tipo de retorno en línea, const half = (n: number): number => n / 2;, o bien le das a la variable un tipo de función y dejas que se infieran los parámetros: const half: (n: number) => number = (n) => n / 2;.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR