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.