Introduction
These notes should help in better understanding TypeScript
and might be helpful when needing to lookup up how leverage TypeScript in a specific situation. All examples are based on TypeScript 3.2.
ReturnType
In the following we will talk about the ReturnType
.
To better understand ReturnType
, we will build examples a long the way, that should display where using this conditional type might be useful.
Before we go into more depth, let's write a basic example to demonstrate how ReturnType
functions.
We have a getInt
function that expects an input of type string
and parses the integer value of given input.
function getInt(a: string) {
return parseInt(a);
}
type A = ReturnType<typeof getInt>; // => number
Using typeof
, we are able to able to get hold of the type signature of getInt
, which in this case would mean (a: string) => number
. ReturnType
accepts a function and returns the return type that the function would return when being invoked. number
in our getInt
example.
Taking this approach has one main advantage, we don't need to keep the return types in sync with our function definition.
Advanced
Now that we have seen what ReturnType
does and how we can leverage this type in it's most basic form, let's try to get a deeper understanding and see more advanced cases.
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
Above, is the ReturnType
implementation. It tries to infer the return value and either returns the inferred type or any
.
In our next example, we want to get a hold of the return type of a function that creates a User
.
let id = 0;
function createUser(name: string, position: string) {
return {
id: id++,
name,
position,
createdAt: new Date()
};
}
type User = ReturnType<typeof createUser>;
// => {id: number, name: string, position: string, createdAt: Date}
Again, ReturnTypes
can return the created User
type. This means we don't have to keep our types in sync with our function declaration.
Here are more examples using ReturnType
.
type A = ReturnType<any>; // => any
type B = ReturnType<never>; // => never
type C = ReturnType<() => string>; //=> string
Our next example is trying to infer the None
type, where we define a constant for None
.
const None = "None";
function none() {
return {type: None}
}
type NoneType = ReturnType<typeof none>; // => {type: string}
Interestingly type NoneType
is defined as {type: string}
but should actually be {type: "None"}
.
To enable TypeScript to infer the correct type, we need to be explicit about the None
constant in this case.
const None = "None";
function none() {
return {type: None as typeof None}
}
type NoneType = ReturnType<typeof none>; // => {type: "None"}
By explicitly defining the None
type, ReturnType
can infer the correct type.
We need to keep this in mind in these specific cases.
Finally, let's see how we might be able to infer the return type of a generic function.
function identity<T>(a: T) : T {
return a;
}
Let's see what ReturnType
can infer:
type IdentityType = ReturnType<typeof identity>; // => {}
type IdentityType = ReturnType<typeof identity<number>>; // => syntax error!
So we can't infer identity.
How do we infer the return type of a generic function then?
We need to implement our own return type as suggested here.
interface Callable<ReturnType> {
(...args: any[]): ReturnType;
}
type GenericReturnType<ReturnType, F> = F extends Callable<ReturnType>
? ReturnType
: never;
Now using our GenericReturnType
we can infer the correct return type:
type IdentityType = GenericReturnType<string, typeof identity>; // => string
type IdentityType = GenericReturnType<number, typeof identity>; // => number
When it comes to generic functions we might be dealing with suboptimal conditions currently. The above GenericReturnType
will work with the identity function but might break when dealing with more complex generic functions.
On a side note, the above GenericReturnType
works due to the fact that interfaces in TypeScript can describe function types. Take a look at the following example:
interface GetInt {
(a: string): number;
}
const getInt: GetInt = (a) => parseInt(a);
type A = ReturnType<typeof getInt>; // => number
We should have a better understanding of how ReturnType
functions now and where we might gain some benefits when using it in our application.
If you have any questions or feedback please leave a comment here or connect via Twitter: A. Sharif
Top comments (1)
Apparently we can't infer the return type of a generic function based on a generic input. One of the guys in the gitter TypeScript channel gave me this example explanation.
^ example however does not actually work how it's explained...