TypeScript Tutorial · Generics and Type Manipulation · Lesson 19 of 25

TypeScript Mapped and Conditional Types

Transform every property with mapped types, and choose types with conditions.

Mapped types

A mapped type loops over keys to build a new type. This is how Partial and Readonly are implemented:

Example 1Try it yourself
type MyReadonly<T> = { readonly [K in keyof T]: T[K] };
type Nullable<T> = { [K in keyof T]: T[K] | null };
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] };

interface Point { x: number; y: number }
const p: MyReadonly<Point> = { x: 1, y: 2 };
const blank: Nullable<Point> = { x: null, y: 5 };
const getters: Getters<Point> = { getX: () => p.x, getY: () => p.y };
console.log(p, blank, getters.getX() + getters.getY());

Output

{ x: 1, y: 2 } { x: null, y: 5 } 3

Conditional types

T extends U ? X : Y picks a type based on a condition. With infer you can extract parts of a type:

Example 2Try it yourself
type IsString<T> = T extends string ? "yes" : "no";
type ElementOf<T> = T extends (infer E)[] ? E : never;
type Unwrap<T> = T extends Promise<infer V> ? V : T;

const a: IsString<"hi"> = "yes";
const b: IsString<42> = "no";
const c: ElementOf<boolean[]> = true;
const d: Unwrap<Promise<number>> = 42;
console.log(a, b, c, d);

Output

yes no true 42

Template literal types

Build string types from other string types:

Example 3Try it yourself
type Method = "get" | "post";
type Resource = "user" | "order";
type Handler = `${Method}${Capitalize<Resource>}`;   // "getUser" | "getOrder" | "postUser" | "postOrder"
type CssSize = `${number}px` | `${number}rem`;

const handlers: Record<Handler, () => string> = {
  getUser: () => "GET /user",
  getOrder: () => "GET /order",
  postUser: () => "POST /user",
  postOrder: () => "POST /order",
};
const gap: CssSize = "1.5rem";
console.log(Object.keys(handlers).length, handlers.postOrder(), gap);

Output

4 POST /order 1.5rem

Test yourself

What does infer do inside a conditional type?

  1. Infers a variable's value
  2. Captures part of a type into a new type variable
  3. Disables type checking
  4. Creates a runtime check
Show answer

Answer: Captures part of a type into a new type variable