I have exported a function from some module that looks like:
export function MyFunc<A>() {
return {
foo: (in: A) => void
}
}
Now, in some other module, I want to be able to talk about the different return types of MyFunc
. Since I didn't export the type, I'll use typeof
to get hold of the type I want given the value MyFunc
. Ideally I would do the following:
import { MyFunc } from "mymodule";
type MyFuncReturned<A> = ReturnType<typeof MyFunc<A>>;
function foo(): MyFuncReturned<string> {
// ...
}
Hrmph, this doesn't work; typeof
can only be passed a value and doesn't like my attempt to specify the generic type of that value.
The best I can do is convincing TypeScript to infer specific types of MyFunc
from values I've created, and then giving them individual type aliases, eg:
const myFuncStringReturn = MyFunc<string>();
type MyFuncStringReturn = typeof myFuncStringReturn;
To avoid actually running MyFunc
just to get the type info, I can hide it behind a function and use ReturnType
on it:
const myFuncStringReturn = () => MyFunc<string>();
type MyFuncStringReturn = ReturnType<typeof myFuncStringReturn>;
const myFuncBoolReturn = () => MyFunc<bool>();
type MyFuncBoolReturn = ReturnType<typeof myFuncBoolReturn>;
This gives me a way of, one type at a time, talking about the different return types of MyFunc
, but it
MyFunc
in a more generic sense.The only "proper" solution I can come up with is duplicating a bunch of type info when I declare MyFunc
:
export function MyFunc<A>(): MyFuncReturns<A> {
return {
foo: (in: A) => void
}
}
export type MyFuncReturns<A> = {
foo: (in: A) => void
}
But now as I change MyFunc
, I have to make sure to keep MyFuncReturns
in sync with it.
Is there any way I can get hold of a type like MyFuncReturns<A>
given just our exported value MyFunc
, without having to add runtime code or add the boilerplate above?
Use the ReturnType utility type to get the return type of a function in TypeScript, e.g. type T = ReturnType<typeof myFunction> . The ReturnType utility type constructs a type that consists of the return type of the provided function type.
The ReturnType in TypeScript is a utility type which is quite similar to the Parameters Type. It let's you take the return output of a function, and construct a type based off it.
Assigning Generic ParametersBy passing in the type with the <number> code, you are explicitly letting TypeScript know that you want the generic type parameter T of the identity function to be of type number . This will enforce the number type as the argument and the return value.
TypeScript supports generic classes. The generic type parameter is specified in angle brackets after the name of the class. A generic class can have generic fields (member variables) or methods.
There is a proposal to allow using typeof
with arbitrary expressions to allow things like getting the return type of a generic functions for a specific type argument (see here and here)
A more generic workaround that works today is to use a generic class with a field that is tied to the return type of the function. We can then extract the field of the class. Since for classes we can specify generic type parameters in type expressions we can extract the generic form of the return type:
export function MyFunc<A>() {
return {
foo: (os : A) => {}
}
}
class Helper <T> {
Return = MyFunc<T>()
}
type FuncReturnType<T> = Helper<T>['Return']
type ForBool = FuncReturnType<boolean> // {foo: (os: boolean) => void;}
type ForString = FuncReturnType<string> // {foo: (os: string) => void;}
Note If you have constraints of A
you will need to duplicate those on T
in Helper
and FuncReturnType
, that is unavoidable unfortunately.
Titian's solution above works great if the Generic is only applied inside the function body.
However, there are cases that the Generic Type is a part of the arguments and/or return type. e.g.
function MyFunc3<T extends object | number | string>(r: number, p: T, x: boolean): T {
return p;
}
So, to generalize Titian's solution and support fixating both the arguments and the return type of any generic function, I wrote the following:
// From https://stackoverflow.com/a/53808212 by jcalz (https://stackoverflow.com/users/2887218)
export type IfEquals<T, U, Y=unknown, N=never> =
(<G>() => G extends T ? 1 : 2) extends
(<G>() => G extends U ? 1 : 2) ? Y : N;
// Aidin: Please comment if you could make the following shorter!
type ReplaceType<T, FROM_TYPE, TO_TYPE> = IfEquals<T, FROM_TYPE, TO_TYPE, T>;
type ReplaceTypeInArray<ARR, F, T> =
ARR extends [] ? []
: ARR extends [infer P0] ? [P0 extends F ? T : P0]
: ARR extends [infer P0, infer P1] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>]
: ARR extends [infer P0, infer P1, infer P2] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>]
: ARR extends [infer P0, infer P1, infer P2, infer P3] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>]
: ARR extends [infer P0, infer P1, infer P2, infer P3, infer P4] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>, ReplaceType<P4, F, T>]
: ARR extends [infer P0, infer P1, infer P2, infer P3, infer P4, infer P5] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>, ReplaceType<P4, F, T>, ReplaceType<P5, F, T>]
: ARR extends [infer P0, infer P1, infer P2, infer P3, infer P4, infer P5, infer P6] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>, ReplaceType<P4, F, T>, ReplaceType<P5, F, T>, ReplaceType<P6, F, T>]
: ARR extends [infer P0, infer P1, infer P2, infer P3, infer P4, infer P5, infer P6, infer P7] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>, ReplaceType<P4, F, T>, ReplaceType<P5, F, T>, ReplaceType<P6, F, T>, ReplaceType<P7, F, T>]
: ARR extends [infer P0, infer P1, infer P2, infer P3, infer P4, infer P5, infer P6, infer P7, infer P8] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>, ReplaceType<P4, F, T>, ReplaceType<P5, F, T>, ReplaceType<P6, F, T>, ReplaceType<P7, F, T>, ReplaceType<P8, F, T>]
: never;
type ALL = string | number | object | boolean;
export function MyFunc1<T extends ALL>() {
return {
foo: (os : T) => {}
}
}
function MyFunc2<T extends ALL>(r: 55, p: T, x: boolean): T {
return p;
}
// Inspired by https://stackoverflow.com/a/52964723 by Titian (https://stackoverflow.com/users/125734)
class Helper1 <T extends ALL> {
Fixate = (...args: ReplaceTypeInArray<Parameters<typeof MyFunc1>, ALL, T>) => MyFunc1<T>(...args);
}
type FixatedFunc1<T extends ALL> = Helper1<T>['Fixate'];
// -- Usage
type ForNumber1 = FixatedFunc1<number> // {foo: (os: number) => void;}
type ForString1 = FixatedFunc1<string> // {foo: (os: string) => void;}
// ~~~~~~~~~~~~~~~~~~~
class Helper2 <T extends ALL> {
Fixate = (...args: ReplaceTypeInArray<Parameters<typeof MyFunc2>, ALL, T>) => MyFunc2<T>(...args);
}
type FixatedFunc2<T extends ALL> = Helper2<T>['Fixate'];
// -- Usage
type ForNumber2 = FixatedFunc2<number> // (args_0: 55, args_1: number, args_2: boolean) => number
type ForString2 = FixatedFunc2<string> // (args_0: 55, args_1: string, args_2: boolean) => string
Playground Link (Contains all 3 parts)
Now, one can simply use ReturnType<T>
or Parameteres<T>
on any of these fixated function types!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With