Más acerca de funciones

Más sobre Funciones en TypeScript

Las funciones son el componente básico de cualquier aplicación, ya sean funciones locales, importadas de otro módulo o métodos de una clase. También son valores y, al igual que otros valores, TypeScript tiene muchas formas de describir cómo se pueden llamar funciones. Aprendamos a escribir tipos que describan funciones.

Expresiones de tipo de función

La forma más sencilla de describir una función es con una expresión de tipo de función. Estos tipos son sintácticamente similares a las funciones de flecha:

Prueba este código ↗

function greeter(fn: (a: string) => void) {
  fn("Hello, World");
}
 
function printToConsole(s: string) {
  console.log(s);
}
 
greeter(printToConsole);

La sintaxis (a: string) => void significa “una función con un parámetro, llamado a, de tipo string, que no tiene un valor de retorno”. Al igual que con las declaraciones de funciones, si no se especifica un tipo de parámetro, implícitamente es any.

Ten en cuenta que el nombre del parámetro es obligatorio. ¡El tipo de función (string) => void significa “una función con un parámetro llamado string de tipo any”!

Por supuesto, podemos usar un alias de tipo para nombrar un tipo de función:

Prueba este código ↗

type GreetFunction = (a: string) => void;
function greeter(fn: GreetFunction) {
  // ...
}

Firmas de llamadas

En JavaScript, las funciones pueden tener propiedades además de ser invocables. Sin embargo, la sintaxis de expresión del tipo de función no permite declarar propiedades. Si queremos describir algo invocable con propiedades, podemos escribir una firma de llamada en un tipo de objeto:

Prueba este código ↗

type DescribableFunction = {
  description: string;
  (someArg: number): boolean;
};
function doSomething(fn: DescribableFunction) {
  console.log(fn.description + " returned " + fn(6));
}
 
function myFunc(someArg: number) {
  return someArg > 3;
}
myFunc.description = "default description";
 
doSomething(myFunc);

Ten en cuenta que la sintaxis es ligeramente diferente en comparación con una expresión de tipo de función; usa : entre la lista de parámetros y el tipo de retorno en lugar de =>.

Firmas de Constructores

Las funciones de JavaScript también se pueden invocar con el operador new. TypeScript se refiere a estos como constructores porque normalmente crean un nuevo objeto. Puedes escribir una firma de constructor agregando la palabra clave new delante de una firma de llamada:

Prueba este código ↗

type SomeConstructor = {
  new (s: string): SomeObject;
};
function fn(ctor: SomeConstructor) {
  return new ctor("hello");
}

Algunos objetos, como el objeto Date de JavaScript, se pueden llamar con o sin new. Puedes combinar firmas de llamadas y constructores en el mismo tipo de forma arbitraria:

Prueba este código ↗

interface CallOrConstruct {
  (n?: number): string;
  new (s: string): Date;
}

Funciones Genéricas

Es común escribir una función donde los tipos de entrada se relacionan con el tipo de salida, o donde los tipos de dos entradas están relacionados de alguna manera. Consideremos por un momento una función que devuelve el primer elemento de una array:

Prueba este código ↗

function firstElement(arr: any[]) {
  return arr[0];
}

Esta función hace su trabajo, pero desafortunadamente tiene el tipo de retorno any. Sería mejor si la función devolviera el tipo del elemento del array.

En TypeScript, los generics se usan cuando queremos describir una correspondencia entre dos valores. Hacemos esto declarando un parámetro de tipo en la firma de la función:

Prueba este código ↗

function firstElement<Type>(arr: Type[]): Type | undefined {
  return arr[0];
}

Al agregar un parámetro de tipo Type a esta función y usarlo en dos lugares, hemos creado un vínculo entre la entrada de la función (el array) y la salida (el valor de retorno). Ahora cuando lo llamamos sale un tipo más específico:

Prueba este código ↗

// s es de tipo 'string'
const s = firstElement(["a", "b", "c"]);
// n es de tipo 'number'
const n = firstElement([1, 2, 3]);
// u es de tipo undefined
const u = firstElement([]);

Inferencia

Ten en cuenta que no tuvimos que especificar Type en este ejemplo. El tipo fue inferido - elegido automáticamente - por TypeScript.

También podemos usar múltiples parámetros de tipo. Por ejemplo, una versión independiente de map se vería así:

Prueba este código ↗

function map<Input, Output>(arr: Input[], func: (arg: Input) => Output): Output[] {
  return arr.map(func);
}
 
// El parámetro 'n' es de tipo 'string'
// 'parsed' es de tipo 'number[]'
const parsed = map(["1", "2", "3"], (n) => parseInt(n));

Ten en cuenta que en este ejemplo, TypeScript podría inferir tanto el tipo del parámetro de tipo Input (del array string dado), como también el tipo de parámetro Output basado en el valor de retorno de la expresión de la función (number).

Restricciones

Hemos escrito algunas funciones genéricas que pueden funcionar con cualquier tipo de valor. A veces queremos relacionar dos valores, pero solo podemos operar con un determinado subconjunto de valores. En este caso, podemos usar una restricción para limitar los tipos de tipos que un parámetro de tipo puede aceptar.

Escribamos una función que devuelva el mayor de dos valores. Para hacer esto, necesitamos una propiedad de length que sea un número. Restringimos el parámetro de tipo a ese tipo escribiendo una cláusula extends:

Prueba este código ↗

function longest<Type extends { length: number }>(a: Type, b: Type) {
  if (a.length >= b.length) {
    return a;
  } else {
    return b;
  }
}
 
// longerArray es de tipo 'number[]'
const longerArray = longest([1, 2], [1, 2, 3]);
// longerString es de tipo 'alice' | 'bob'
const longerString = longest("alice", "bob");
// Error! Numbers no tiene una propiedad 'length'
const notOK = longest(10, 100);
Error generado
Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.

Hay algunas cosas interesantes a tener en cuenta en este ejemplo. Permitimos que TypeScript infiera el tipo de retorno longest. La inferencia de tipos de retorno también funciona en funciones genéricas.

Debido a que restringimos Type a { length: number }, se nos permitió acceder a la propiedad .length de los parámetros a y b. Sin la restricción de tipo, no podríamos acceder a esas propiedades porque los valores podrían haber sido de otro tipo sin una propiedad length.

Los tipos de longerArray y longerString se infirieron en función de los argumentos. Recuerda, los generics consisten en relacionar dos o más valores con el mismo tipo.

Finalmente, tal como nos gustaría, la llamada a longest(10, 100) se rechaza porque el tipo number no tiene una propiedad .length.

Trabajar con valores restringidos

Aquí hay un error común cuando se trabaja con restricciones genéricas:

Prueba este código ↗

function minimumLength<Type extends { length: number }>(
  obj: Type,
  minimum: number
): Type {
  if (obj.length >= minimum) {
    return obj;
  } else {
    return { length: minimum };
  }
}
Error generado
Type '{ length: number; }' is not assignable to type 'Type'.
  '{ length: number; }' is assignable to the constraint of type 'Type', but 'Type' could be instantiated with a different subtype of constraint '{ length: number; }'.

Podría parecer que esta función está bien: Type está restringido a { length: number }, y la función devuelve Type o un valor que coincida con esa restricción. El problema es que la función promete devolver el mismo tipo de objeto que se pasó, no solo algún objeto que coincida con la restricción. Si este código fuera legal, podrías escribir código que definitivamente no funcionaría:

Prueba este código ↗

// 'arr' obtiene el valor { length: 6 }
const arr = minimumLength([1, 2, 3], 6);
// y falla aquí porque el array tiene un método
// 'slice', ¡pero no el objeto devuelto!
console.log(arr.slice(0));

Especificar argumentos de tipo

TypeScript generalmente puede inferir los argumentos de tipo deseados en una llamada genérica, pero no siempre. Por ejemplo, digamos que escribiste una función para combinar dos arrays:

Prueba este código ↗

function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
  return arr1.concat(arr2);
}

Normalmente sería un error llamar a esta función con arrays que no coinciden:

Prueba este código ↗

const arr = combine([1, 2, 3], ["hello"]);
Error generado
Type 'string' is not assignable to type 'number'.

Sin embargo, si tenías la intención de hacer esto, puedes especificar manualmente Type:

Prueba este código ↗

const arr = combine<string | number>([1, 2, 3], ["hello"]);

Pautas para escribir buenas funciones genéricas

Escribir funciones genéricas es divertido y puede ser fácil dejarse llevar por los parámetros de tipo. Tener demasiados parámetros de tipo o usar restricciones donde no son necesarias puede hacer que la inferencia sea menos exitosa y frustrar a quienes llaman a tu función.

Empuja los parámetros de tipo hacia abajo

Aquí hay dos formas de escribir una función que parecen similares:

Prueba este código ↗

function firstElement1<Type>(arr: Type[]) {
  return arr[0];
}
 
function firstElement2<Type extends any[]>(arr: Type) {
  return arr[0];
}
 
// a: number (bien)
const a = firstElement1([1, 2, 3]);
// b: any (mal)
const b = firstElement2([1, 2, 3]);

Pueden parecer idénticas a primera vista, pero firstElement1 es una forma mucho mejor de escribir esta función. Su tipo de retorno inferido es Type, pero el tipo de retorno inferido de firstElement2 es any porque TypeScript tiene que resolver la expresión arr[0] usando la restricción de tipo, en lugar de “esperar” para resolver el elemento durante una llamada.

Regla: Cuando sea posible, usa el parámetro de tipo en sí en lugar de restringirlo

Usa menos parámetros de tipo

Aquí tienes otro par de funciones similares:

Prueba este código ↗

function filter1<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] {
  return arr.filter(func);
}
 
function filter2<Type, Func extends (arg: Type) => boolean>(
  arr: Type[],
  func: Func
): Type[] {
  return arr.filter(func);
}

Hemos creado un parámetro de tipo Func que no relaciona dos valores. Eso siempre es una señal de alerta, porque significa que las personas que invocan y desean especificar argumentos de tipo tienen que especificar manualmente un argumento de tipo adicional sin ningún motivo. ¡Func no hace nada más que hacer que la función sea más difícil de leer y razonar!

Regla: Utiliza siempre la menor cantidad de parámetros de tipo posible

Los parámetros de tipo deberían aparecer dos veces

A veces olvidamos que es posible que no sea necesario que una función sea genérica:

Prueba este código ↗

function greet<Str extends string>(s: Str) {
  console.log("Hello, " + s);
}
 
greet("world");

Fácilmente podríamos haber escrito una versión más simple:

Prueba este código ↗

function greet(s: string) {
  console.log("Hello, " + s);
}

Recuerda, los parámetros de tipo son para relacionar los tipos de múltiples valores. Si un parámetro de tipo solo se usa una vez en la firma de la función, no relaciona nada. Esto incluye el tipo de devolución inferido; por ejemplo, si Str fuera parte del tipo de retorno inferido de greet, estaría relacionando el argumento y los tipos de retorno, por lo que se usaría dos veces a pesar de aparecer solo una vez en el código escrito.

Regla: si un parámetro de tipo solo aparece en una ubicación, reconsidera seriamente si realmente lo necesitas

Parámetros opcionales

Las funciones en JavaScript a menudo toman una cantidad variable de argumentos. Por ejemplo, el método toFixed de number toma un recuento de dígitos opcional:

Prueba este código ↗

function f(n: number) {
  console.log(n.toFixed()); // 0 arguments
  console.log(n.toFixed(3)); // 1 argument
}

Podemos modelar esto en TypeScript marcando el parámetro como opcional con ?:

Prueba este código ↗

function f(x?: number) {
  // ...
}
f(); // OK
f(10); // OK

Aunque el parámetro se especifica como tipo number, el parámetro x en realidad tendrá el tipo number | undefined porque los parámetros no especificados en JavaScript obtienen el valor undefined.

También puedes proporcionar un parámetro predeterminado:

Prueba este código ↗

function f(x = 10) {
  // ...
}

Ahora en el cuerpo de f, x tendrá el tipo number porque cualquier argumento undefined será reemplazado por 10. Ten en cuenta que cuando un parámetro es opcional, los invocadores (callers) siempre pueden pasar undefined, ya que esto simplemente simula un argumento “faltante”:

Prueba este código ↗

declare function f(x?: number): void;
// cut
// All OK
f();
f(10);
f(undefined);

Parámetros opcionales en Callbacks

Una vez que hayas aprendido acerca de los parámetros opcionales y las expresiones de tipo de función, es muy fácil cometer los siguientes errores al escribir funciones que invocan devoluciones de llamada (callbacks):

Prueba este código ↗

function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i], i);
  }
}

Lo que la gente normalmente pretende cuando escribe index? como parámetro opcional es que quiere que ambas llamadas sean legales:

Prueba este código ↗

myForEach([1, 2, 3], (a) => console.log(a));
myForEach([1, 2, 3], (a, i) => console.log(a, i));

Lo que esto en realidad significa es que callback podría invocarse con un argumento. En otras palabras, la definición de la función dice que la implementación podría verse así:

Prueba este código ↗

function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
  for (let i = 0; i < arr.length; i++) {
    // No tengo ganas de proporcionar el índice hoy.
    callback(arr[i]);
  }
}

A su vez, TypeScript aplicará este significado y emitirá errores que en realidad no son posibles:

Prueba este código ↗

myForEach([1, 2, 3], (a, i) => {
  console.log(i.toFixed());
});
Error generado
'i' is possibly 'undefined'.

En JavaScript, si llamas a una función con más argumentos que parámetros, los argumentos adicionales simplemente se ignoran. TypeScript se comporta de la misma manera. Las funciones con menos parámetros (del mismo tipo) siempre pueden reemplazar funciones con más parámetros.

Regla: Al escribir un tipo de función para una devolución de llamada, nunca escribas un parámetro opcional a menos que tengas la intención de llamar la función sin pasar ese argumento

Sobrecargas de funciones

Algunas funciones de JavaScript se pueden llamar en una variedad de tipos y recuentos de argumentos. Por ejemplo, podrías escribir una función para producir un Date que tome un timestamp (un argumento) o una especificación de mes/día/año (tres argumentos).

En TypeScript, podemos especificar una función que se puede llamar de diferentes maneras escribiendo firmas de sobrecarga. Para hacer esto, escribe una cierta cantidad de firmas de función (generalmente dos o más), seguidas del cuerpo de la función:

Prueba este código ↗

function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
  if (d !== undefined && y !== undefined) {
    return new Date(y, mOrTimestamp, d);
  } else {
    return new Date(mOrTimestamp);
  }
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
const d3 = makeDate(1, 3);
Error generado
No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.

En este ejemplo, escribimos dos sobrecargas: una que acepta un argumento y otra que acepta tres argumentos. Estas dos primeras firmas se denominan firmas de sobrecarga.

Luego, escribimos una implementación de función con una firma compatible. Las funciones tienen una firma de implementación, pero esta firma no se puede llamar directamente. Aunque escribimos una función con dos parámetros opcionales después del requerido, ¡no se puede llamar con dos parámetros!

Sobrecarga de firmas y firma de implementación

Esta es una fuente común de confusión. A menudo la gente escribe código como este y no entiende por qué hay un error:

Prueba este código ↗

function fn(x: string): void;
function fn() {
  // ...
}
// Se espera poder llamar sin argumentos
fn();
Error generado
Expected 1 arguments, but got 0.

Nuevamente, la firma utilizada para escribir el cuerpo de la función no se puede “ver” desde afuera.

La firma de la implementación no es visible desde el exterior. Al escribir una función sobrecargada, siempre debe tener dos o más firmas encima de la implementación de la función.

La firma de implementación también debe ser compatible con las firmas de sobrecarga. Por ejemplo, estas funciones tienen errores porque la firma de implementación no coincide con las sobrecargas de manera correcta:

Prueba este código ↗

function fn(x: boolean): void;
// El tipo de argumento no es correcto
function fn(x: string): void;
function fn(x: boolean) {}
Error generado
This overload signature is not compatible with its implementation signature.

Prueba este código ↗

function fn(x: string): string;
// El tipo de devolución no es correcto
function fn(x: number): boolean;
function fn(x: string | number) {
  return "oops";
}
Error generado
This overload signature is not compatible with its implementation signature.

Escribir buenas sobrecargas

Al igual que los generics, hay algunas pautas que debes seguir al usar sobrecargas de funciones. Seguir estos principios hará que tu función sea más fácil de llamar, de entender y de implementar.

Consideremos una función que devuelve la longitud de una cadena o un array:

Prueba este código ↗

function len(s: string): number;
function len(arr: any[]): number;
function len(x: any) {
  return x.length;
}

Esta función está bien; podemos invocarlo con cadenas o arrays. Sin embargo, no podemos invocarlo con un valor que podría ser una cadena o un array, porque TypeScript solo puede resolver una llamada de función a una única sobrecarga:

Prueba este código ↗

len(""); // OK
len([0]); // OK
len(Math.random() > 0.5 ? "hello" : [0]);
Error generado
No overload matches this call.
  Overload 1 of 2, '(s: string): number', gave the following error.
    Argument of type 'number[] | "hello"' is not assignable to parameter of type 'string'.
      Type 'number[]' is not assignable to type 'string'.
  Overload 2 of 2, '(arr: any[]): number', gave the following error.
    Argument of type 'number[] | "hello"' is not assignable to parameter of type 'any[]'.
      Type 'string' is not assignable to type 'any[]'.

Debido a que ambas sobrecargas tienen el mismo número de argumentos y el mismo tipo de retorno, podemos escribir una versión no sobrecargada de la función:

Prueba este código ↗

function len(x: any[] | string) {
  return x.length;
}

¡Esto es mucho mejor! Los callers pueden invocar esto con cualquier tipo de valor y, como beneficio adicional, no tenemos que encontrar una firma de implementación correcta.

Prefiere siempre parámetros con tipos de unión en lugar de sobrecargas cuando sea posible

Declarando this en una Función

TypeScript inferirá cuál debería ser this en una función mediante el análisis de flujo de código, por ejemplo en lo siguiente:

Prueba este código ↗

const user = {
  id: 123,
 
  admin: false,
  becomeAdmin: function () {
    this.admin = true;
  },
};

TypeScript entiende que la función user.becomeAdmin tiene un this correspondiente, que es el objeto externo user. this, puede ser suficiente para muchos casos, pero hay muchos casos en los que necesitas más control sobre qué objeto representa this. La especificación de JavaScript establece que no puedes tener un parámetro llamado this, por lo que TypeScript usa ese espacio de sintaxis para permitirte declarar el tipo de this en el cuerpo de la función.

Prueba este código ↗

interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}
 
const db = getDB();
const admins = db.filterUsers(function (this: User) {
  return this.admin;
});

Este patrón es común con las API de estilo callback, donde normalmente otro objeto controla cuándo se llama a tu función. Ten en cuenta que necesitas usar function y no funciones de flecha para obtener este comportamiento:

Prueba este código ↗

interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}
 
const db = getDB();
const admins = db.filterUsers(() => this.admin);
Error generado
The containing arrow function captures the global value of 'this'.Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.

Otros tipos que debes conocer

Hay algunos tipos adicionales que querrás reconocer y que aparecen con frecuencia cuando trabajas con tipos de funciones. Como todos los tipos, puedes usarlos en todas partes, pero son especialmente relevantes en el contexto de las funciones.

El tipo void

El tipo void representa el valor de retorno de funciones que no devuelven un valor. Es el tipo inferido cada vez que una función no tiene declaraciones return o no devuelve ningún valor explícito de esas declaraciones de retorno:

Prueba este código ↗

// El valor de retorno inferido es void
function noop() {
  return;
}

En JavaScript, una función que no devuelve ningún valor devolverá implícitamente el valor undefined. Sin embargo, void y undefined no son lo mismo en TypeScript. Hay más detalles al final de este capítulo.

void no es lo mismo que undefined.

El tipo object

El tipo especial object se refiere a cualquier valor que no sea primitivo (string, number, bigint, boolean, symbol, null, o undefined). Esto es diferente del tipo de objeto vacío { }, y también diferente del tipo global Object. Es muy probable que nunca utilices Object.

object no es Object. ¡Utiliza siempre object!

Ten en cuenta que en JavaScript, los valores de las funciones son objetos: tienen propiedades, tienen Object.prototype en su cadena de prototipo, son instanceof Object, puedes llamar a Object.keys en ellos, etcétera. Por esta razón, los tipos de funciones se consideran objects en TypeScript.

El tipo unknown

El tipo unknown representa cualquier valor. Esto es similar al tipo any, pero es más seguro porque no es legal hacer nada con un valor unknown:

Prueba este código ↗

function f1(a: any) {
  a.b(); // OK
}
function f2(a: unknown) {
  a.b();
}
Error generado
'a' is of type 'unknown'.

Esto es útil al describir tipos de funciones porque puedes describir funciones que aceptan cualquier valor sin tener ningún valor any en el cuerpo de tu función.

A la inversa, puedes describir una función que devuelve un valor de tipo unknown:

Prueba este código ↗

function safeParse(s: string): unknown {
  return JSON.parse(s);
}
 
// ¡Hay que tener cuidado con 'obj'!
const obj = safeParse(someRandomString);

El tipo never

Algunas funciones nunca devuelven un valor:

Prueba este código ↗

function fail(msg: string): never {
  throw new Error(msg);
}

El tipo never representa valores que nunca se observan. En un tipo de retorno, esto significa que la función genera una excepción o finaliza la ejecución del programa.

never también aparece cuando TypeScript determina que no queda nada en una unión.

Prueba este código ↗

function fn(x: string | number) {
  if (typeof x === "string") {
    // hacer algo
  } else if (typeof x === "number") {
    // hacer otra cosa
  } else {
    x; // tiene el tipo 'never'!
  }
}

El tipo Function

El tipo global Function describe propiedades como bind, call, apply y otras presentes en todos los valores de funciones en JavaScript. También tiene la propiedad especial de que siempre se pueden llamar valores de tipo Function; estas llamadas devuelven any:

Prueba este código ↗

function doSomething(f: Function) {
  return f(1, 2, 3);
}

Esta es una llamada a función sin tipo y generalmente es mejor evitarla debido al tipo de retorno inseguro any.

Si necesitas aceptar una función arbitraria pero no tienes intención de llamarla, el tipo () => void es generalmente más seguro.

Parámetros y argumentos Rest

Lectura en segundo plano: Rest Parameters ↗ Syntax difundido ↗

Parámetros rest

Además de usar parámetros opcionales o sobrecargas para crear funciones que puedan aceptar una cantidad de argumentos fijos, también podemos definir funciones que toman un número ilimitado de argumentos usando parámetros rest.

Un parámetro rest aparece después de todos los demás parámetros y usa la sintaxis ...:

Prueba este código ↗

function multiply(n: number, ...m: number[]) {
  return m.map((x) => n * x);
}
// 'a' toma el valor [10, 20, 30, 40]
const a = multiply(10, 1, 2, 3, 4);

En TypeScript, la anotación de tipo en estos parámetros es implícitamente any[] en lugar de any, y cualquier anotación de tipo proporcionada debe tener la forma Array<T> o T [], o un tipo de tupla (del que aprenderemos más adelante).

Argumentos Rest

A la inversa, podemos proporcionar un número variable de argumentos de un objeto iterable (por ejemplo, un array) usando la sintaxis spread. Por ejemplo, el método push de arrays toma cualquier número de argumentos:

Prueba este código ↗

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
arr1.push(...arr2);

Ten en cuenta que, en general, TypeScript no asume que los arrays sean inmutables. Esto puede llevar a algunos comportamientos sorprendentes:

Prueba este código ↗

// El tipo inferido es number[] -- "un array con cero o más numbers",
// no específicamente dos numbers
const args = [8, 5];
const angle = Math.atan2(...args);
Error generado
A spread argument must either have a tuple type or be passed to a rest parameter.

La mejor solución para esta situación depende un poco de tu código, pero en general un contexto const es la solución más sencilla:

Prueba este código ↗

// Inferido como una tupla de longitud 2
const args = [8, 5] as const;
// OK
const angle = Math.atan2(...args);

El uso de argumentos rest puede requerir activar downlevelIteration cuando apuntas a runtimes más antiguos.

Desestructuración de parámetros

Lectura previa: Asignación de desestructuración ↗

Puedes usar la desestructuración de parámetros para descomprimir convenientemente objetos proporcionados como argumento en una o más variables locales en el cuerpo de la función. En JavaScript, se ve así:

function sum({ a, b, c }) {
  console.log(a + b + c);
}
sum({ a: 10, b: 3, c: 9 });

La anotación de tipo para el objeto va después de la sintaxis de desestructuración:

Prueba este código ↗

function sum({ a, b, c }: { a: number; b: number; c: number }) {
  console.log(a + b + c);
}

Esto puede parecer un poco verboso, pero aquí también puedes usar un tipo con nombre:

Prueba este código ↗

// Lo mismo que el ejemplo anterior
type ABC = { a: number; b: number; c: number };
function sum({ a, b, c }: ABC) {
  console.log(a + b + c);
}

Asignabilidad de Funciones

Tipo de retorno void

El tipo de retorno void para funciones puede producir un comportamiento inusual pero esperado.

El tipado contextual con un tipo de retorno void no obliga a las funciones a no devolver algo. Otra forma de decir esto es un tipo de función contextual con un tipo de retorno void (type voidFunc = () => void), cuando se implementa, puede devolver cualquier otro valor, pero será ignorado.

Así, las siguientes implementaciones del tipo () => void son válidas:

Prueba este código ↗

type voidFunc = () => void;
 
const f1: voidFunc = () => {
  return true;
};
 
const f2: voidFunc = () => true;
 
const f3: voidFunc = function () {
  return true;
};

Y cuando el valor de retorno de una de estas funciones se asigna a otra variable, conservará el tipo de void:

Prueba este código ↗

const v1 = f1();
 
const v2 = f2();
 
const v3 = f3();

Este comportamiento existe para que el siguiente código sea válido aunque Array.prototype.push devuelva un número y el método Array.prototype.forEach espere una función con un tipo de retorno de void.

Prueba este código ↗

const src = [1, 2, 3];
const dst = [0];
 
src.forEach((el) => dst.push(el));

Hay otro caso especial que debes tener en cuenta: cuando la definición de una función literal tiene un tipo de retorno void, esa función no debe devolver nada.

Prueba este código ↗

function f2(): void {
  // @ts-expect-error
  return true;
}
 
const f3 = function (): void {
  // @ts-expect-error
  return true;
};

Para obtener más información sobre void, consulta estas otras entradas de documentación:

Última actualización