TypeScript Conditional Types

Apr 05, 2023#typescript

Conditional types are a feature of the TypeScript language that allow for the creation of types that depend on the evaluation of other types. They are created using the extends keyword to define a type based on a conditional check.

They have the following syntax:

Type extends Condition ? TrueType : FalseType

This means that if Type is assignable to Condition, then the conditional type evaluates to TrueType; otherwise, it evaluates to FalseType.

For example, suppose we have the following types:

interface Animal {
  live(): void;
}

interface Dog extends Animal {
  woof(): void;
}

We can use a conditional type to create a new type that depends on whether a type is a subtype of Animal or not:

type Example1 = Dog extends Animal ? number : string; // number
type Example2 = RegExp extends Animal ? number : string; // string

Conditional types are especially useful when combined with generics, because they can express complex type transformations based on generic parameters. For example, suppose we have a function that creates labels for different types of objects:

interface IdLabel {
  id: number; // some fields
}

interface NameLabel {
  name: string; // other fields
}

function createLabel<T>(idOrName: T): T extends number ? IdLabel : NameLabel {
  // implementation
}

This function uses a conditional type to return different types of labels based on whether the input is a number or a string. If we call it with a number, we get an IdLabel; if we call it with a string, we get a NameLabel:

let a = createLabel("typescript"); // NameLabel
let b = createLabel(2.8); // IdLabel
let c = createLabel(Math.random() ? "hello" : 42); // NameLabel | IdLabel

Conditional types can also be distributive over union types, which means that they apply to each member of the union separately and produce a new union type. For example, suppose we have the following type:

type Pet = Dog | Cat;

We can use a conditional type to create a new type that maps each Pet to its sound:

type PetSound<T> = T extends Dog ? "woof" : "meow";
type Sound = PetSound<Pet>; // "woof" | "meow"

Conditional types can also be nested, recursive, and inferential.