rena.to logo image
·
short
·

TypeScript Distributives

Get it once and for all

TL;DR:
Given the following `Token` union
type Token = 'x' | 'y' | 'z'
Let's say you want to create a new shape from `Token` that's something like this:
type Discriminated = { kind: 'x' } | { kind: 'y' } | { kind: 'z' }
And you try the most obvious way
type Map<T extends Token> = { kind: T };
It won't work because it won't "distribute" Token members correctly. It will create a single `{ kind: T }` with every T member:
type T = Map<Token>;
// ^? type T = { kind: 'x' | 'y' | 'z' }
We will need to use distribute the union in a way each member is treated individually, like `K in T`:
type Map<T extends Token> = { [K in T]: { kind: K } }[T]
type T = Map<Token>
// ^? type T = { kind: 'x' } | { kind: 'y' } | { kind: 'z' }
Using conditional type (a single ternary T extends any) also does the distributive because the condition checks each member individually:
type Map<T extends Union> = T extends any ? { kind: T } : never;
type T = Map<Token>;
// ^? type T = { kind: 'x' } | { kind: 'y' } | { kind: 'z' }
In other words:
type Map<T extends Union> = { kind: T };
// ^ this T is a whole union 'x' | 'y' | 'z'
type Map<T extends Union> = T extends any ? { kind: T } : never;
// ^ this T represents a single union member eg 'y'
type Map<T extends Union> = { [P in T]: { kind: P } }[T]
// ^ this P is a single union member of T eg 'y'
Follow me on twitter, and github. Leave a comment.