loading...

Notes on TypeScript: ReturnType

busypeoples profile image A. Sharif Updated on ・3 min read

Notes on TypeScript (17 Part Series)

1) Notes on TypeScript: Pick, Exclude and Higher Order Components 2) Notes on TypeScript: Render Props 3 ... 15 3) Notes on TypeScript: Accessing Non Exported Component Prop Types 4) Notes on TypeScript: ReturnType 5) Notes on TypeScript: Phantom Types 6) Notes on TypeScript: Type Level Programming Part 1 7) Notes on TypeScript: Conditional Types 8) Notes on TypeScript: Mapped Types and Lookup Types 9) Notes on TypeScript: React and Generics 10) Notes on TypeScript: Fundamentals For Getting Started 11) Notes on TypeScript: Type Level Programming Part 2 12) Notes on TypeScript: Inferring React PropTypes 13) Notes on TypeScript: React Hooks 14) Notes on TypeScript: Recursive Type Aliases and Immutability 15) Notes on TypeScript: Handling Side-Effects 16) Notes on TypeScript: Type Level Programming Part 3 17) Notes on TypeScript: Building a validation library

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

Notes on TypeScript (17 Part Series)

1) Notes on TypeScript: Pick, Exclude and Higher Order Components 2) Notes on TypeScript: Render Props 3 ... 15 3) Notes on TypeScript: Accessing Non Exported Component Prop Types 4) Notes on TypeScript: ReturnType 5) Notes on TypeScript: Phantom Types 6) Notes on TypeScript: Type Level Programming Part 1 7) Notes on TypeScript: Conditional Types 8) Notes on TypeScript: Mapped Types and Lookup Types 9) Notes on TypeScript: React and Generics 10) Notes on TypeScript: Fundamentals For Getting Started 11) Notes on TypeScript: Type Level Programming Part 2 12) Notes on TypeScript: Inferring React PropTypes 13) Notes on TypeScript: React Hooks 14) Notes on TypeScript: Recursive Type Aliases and Immutability 15) Notes on TypeScript: Handling Side-Effects 16) Notes on TypeScript: Type Level Programming Part 3 17) Notes on TypeScript: Building a validation library

Posted on Feb 24 '19 by:

busypeoples profile

A. Sharif

@busypeoples

Focusing on quality. Software Development. Product Management. https://twitter.com/sharifsbeat

Discussion

markdown guide
 

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.

function identity<T>(a: T) : T {
  return a;
}
interface Callable<ReturnType> {
  (...args: any[]): ReturnType;
}
type GenericReturnType<ReturnType, F> = F extends Callable<ReturnType>
  ? ReturnType
  : never;

type IdentityType = GenericReturnType<string, typeof identity>; // => string

^ example however does not actually work how it's explained...

function identity<T>(a: T) : Promise<T> {
  return Promise.resolve(a);
}
interface Callable<ReturnType> {
  (...args: any[]): ReturnType;
}
type GenericReturnType<ReturnType, F> = F extends Callable<ReturnType>
  ? ReturnType
  : never;

type IdentityType = GenericReturnType<string, typeof identity>; // never