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:
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 llamadostring
de tipoany
”!
Por supuesto, podemos usar un alias de tipo para nombrar un tipo de función:
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:
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:
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:
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:
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:
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:
// 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í:
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
:
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);
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:
function minimumLength<Type extends { length: number }>(
obj: Type,
minimum: number
): Type {
if (obj.length >= minimum) {
return obj;
} else {
return { length: minimum };
}
}
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:
// '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:
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:
const arr = combine([1, 2, 3], ["hello"]);
Type 'string' is not assignable to type 'number'.
Sin embargo, si tenías la intención de hacer esto, puedes especificar manualmente Type
:
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:
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:
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:
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:
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:
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 ?
:
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:
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”:
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):
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:
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í:
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:
myForEach([1, 2, 3], (a, i) => {
console.log(i.toFixed());
});
'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:
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);
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:
function fn(x: string): void;
function fn() {
// ...
}
// Se espera poder llamar sin argumentos
fn();
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:
function fn(x: boolean): void;
// El tipo de argumento no es correcto
function fn(x: string): void;
function fn(x: boolean) {}
This overload signature is not compatible with its implementation signature.
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";
}
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:
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:
len(""); // OK
len([0]); // OK
len(Math.random() > 0.5 ? "hello" : [0]);
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:
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:
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.
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:
interface DB {
filterUsers(filter: (this: User) => boolean): User[];
}
const db = getDB();
const admins = db.filterUsers(() => this.admin);
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:
// 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 queundefined
.
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 esObject
. ¡Utiliza siempreobject
!
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 object
s 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
:
function f1(a: any) {
a.b(); // OK
}
function f2(a: unknown) {
a.b();
}
'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
:
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:
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.
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
:
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 ...
:
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:
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:
// 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);
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:
// 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:
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:
// 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:
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
:
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
.
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.
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: