Búsqueda de sitios web

typescript - Tipo de retorno de función curryada


Usar ReturnType<T> en una función devuelve, como cabría esperar, el tipo de retorno.

type F = () => string
//ReturnType<F> === string

Además, como cabría esperar, al usar funciones curry el tipo de retorno es

type FF = () => () => string
//ReturnType<FF> === () => string

¿Hay una manera de crear un tipo genérico que devuelva el retorno final? Es decir

type FF = () => () => string
//FinalReturnType<FF> === string

Respuesta #1

Esto funciona actualmente (TS 3.7), pero el equipo de TS no lo permitió intencionalmente y no ha decidido dar soporte. Puede fallar en versiones futuras.

type FinalReturnType<T> = {
  0: T,
  1: T extends (...args: any) => infer R ? FinalReturnType<R> : T,
}[T extends (...args: any) => infer _ ? 1 : 0];

Tiene que ser tan extraño y feo porque actualmente hay una restricción que dice que un tipo solo puede llamarse a sí mismo si está anidado dentro de otro tipo estructural (en este caso, un objeto con dos claves), pero luego inmediatamente extraemos la propiedad que necesitamos de la tupla.

Cómo se vería idealmente:

Si el lenguaje soportara este tipo de mecanismo, pensaría que sería tan simple como:

type FinalReturnType<T> = T extends (...args: any) => infer R
                              ? FinalReturnType<R> 
                              : T;

En otras palabras, usa una definición recursiva. Si T es una función, se llama a sí misma pasando el tipo de retorno de la función. Si no es una función, devuelve T en sí mismo. Por ejemplo:

FinalReturnType<string> // -> string

Y así se termina la recursión: cuando has desenvuelto todas las capas de funciones que devuelven funciones, eventualmente llegas a una no-función.

Desafortunadamente, esto no es posible, produciendo el error:

Type alias 'FinalReturnType' circularly references itself.

Hubo un cambio para habilitar referencias de tipo recursivas, pero no habilita este escenario.

Hay una discusión abierta sobre un apoyo más general para la recursión.

La situación actual es que solo puedes compilar mi respuesta si agregas [] alrededor de la llamada a sí mismo para convertirla en una tupla:

type FinalReturnType<T> = T extends (...args: any) => infer R
                                ? [FinalReturnType<R>] 
    : T;

type t1 = FinalReturnType<string>;  // -> string

type F = () => string
type t2 = FinalReturnType<F>;       // -> [string]

type FF = () => () => string
type t3 = FinalReturnType<FF>;      // -> [[string]]

Como puedes ver, esto vuelve a envolver los resultados en capas de tuplas anidadas, y estás de nuevo con el mismo problema, porque tampoco puedes desenvolverlas.

Actualmente, puedes usar el truco de envolver en un objeto con dos propiedades, y luego desenvolver inmediatamente accediendo a la correcta, como se describe al principio de esta respuesta. Pero esto se habilitó por accidente, y el equipo de TS no está seguro de si quieren soportar/fomentar esto.