Skip to main content

Real World Example of type infer

What is type infer?

Typescript operator allows us to extract a type from another type. We can see an obvious example from the typescript built-in ReturnType:

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

This means that if passed in T is a function type, then the R type is the return type of the function. The it uses the conditional type extends to check if the R can be successfully extracted from T. If it can be extracted, then return R type. Otherwise, return any type.

As can be seen, the part infer R section allows us extracting a type (R) inside another type (T).

Another very common usage of infer operator is to extract the element type inside an array.

// Extract the element type inside an array
type ArrayElement<T extends Array<any>> = T extends Array<infer U> ? U : never;

// Example of number array
const numberArray = [1, 2, 3];
type ExtractedArrayType = ArrayElement<typeof numberArray>; // number

// Example of string array
const stringArray = ["a", "b", "c"];
type ExtractedStringArrayType = ArrayElement<typeof stringArray>; // string

In this article, we will use a more complex real world example to demonstrate how to use the infer operator when we want typescript to tell us what action parents will take according to the behavior of children.

The Our task is to have a WhatParentShouldDo type that represents what action parents will take according to the behavior of children.

// A child can call "mother" or "father" when he/she want to eat something.
type ChildCallMon = {
call: "mother";
wantToEat: CookFoodCategory;
};
type ChildCallDad = {
call: "father";
wantToEat: BuyFoodCategory;
};

// The behavior of children could be either "ChildCallMon" or "ChildCallDad"
type ChildrenBehavior = ChildCallMon | ChildCallDad;

// Parents can either cook food or buy food for children. But the type of food is different.
type DadBuyFood = (args: BuyFoodCategory) => void;
type BuyFoodCategory = "Humbugger" | "Pizza" | "Sushi";

type MomCookFood = (args: CookFoodCategory) => void;
type CookFoodCategory = "rice" | "noodle" | "soup";

type WhatParentShouldDo<T extends ChildrenBehavior> = TBD; // TODO: implement this type

In the above example, the child will call "mother" when he/she want to eat CookFoodCategory and call "father" when he/she want to eat BuyFoodCategory.

So, we then want to create a type WhatParentShouldDo that represents what action parents will take according to the behavior of children.

It is where the infer operator comes in handy.

How infer operator works?

We will use the infer operator to implement the WhatParentShouldDo type. The U type in the following code is the type that we want to extract from the ChildrenBehavior type. So the U type will be either "mother" or "father". Then we can use the conditional types to return the corresponding type: When U is "mother", then return MomCookFood type. When U is "father", then return DadBuyFood type.

type WhatParentShouldDo<T extends ChildrenBehavior> = T extends { call: infer U }
? U extends "mother"
? MomCookFood
: DadBuyFood
: never;

We can start implementing the WhatParentShouldDo type. --> Dad's action and Mom's action. Then finally we can implement the parentAction function that takes ChildrenBehavior as an argument.

const DadTakeAction: WhatParentShouldDo<ChildCallDad> = (args) => {
console.log(`Dad is going out to buy ${args}`); // BuyFoodCategory
// Do something to get task done
};
const MonTakeAction: WhatParentShouldDo<ChildCallMon> = (args) => {
console.log(`Mom is going to cook ${args}`); // CookFoodCategory
// Do something to get task done
};

const parentActionChecker = (args: ChildrenBehavior): args is ChildCallMon => {
return args.call === "mother";
};

export const parentAction = (args: ChildrenBehavior) => {
if (parentActionChecker(args)) {
MonTakeAction(args.wantToEat);
} else {
DadTakeAction(args.wantToEat);
}
};

In this case, we firstly use typescript [User-defined type guard](user-defined type guard) to implement a type guard function parentActionChecker that checks if the ChildrenBehavior is ChildCallMon type. Then we can use the type guard function in the parentAction and call the corresponding function.

Congratulations! We used many typescript features (user-defined type guard / conditional types / infer operator) to successfully implement this example. Hope this article helps you to understand more about infer operator in typescript.