Last active
January 23, 2022 12:03
-
-
Save robinpokorny/9e52e4c4d4801297099d6f0620ff2012 to your computer and use it in GitHub Desktop.
Tagged Unions in TypeScript Showcase
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// And you will know my name is TypeScript | |
// When I lay my Tagged Union upon thee | |
// by @robinpokorny | |
// 1. Union of literals | |
// The cornerstone of any nutritious breakfast. | |
type Burger1 = `McDonalds` | `BigKahuna`; | |
const brettsBreakfast1_1: Burger1 = `BigKahuna`; | |
const brettsBreakfast1_2: Burger1 = `Whooper`; // Caught | |
// 2. Mixed approach | |
// Let's say BigKahuna Burgers are numbered | |
type Burger2 = { | |
brand: `McDonalds` | `BigKahuna`; | |
name: `Le Big Mac` | `1/4 pounder` | `Cheese Royal` | number; | |
}; | |
const brettsBreakfast2_1: Burger2 = { | |
brand: `McDonalds`, | |
name: `Le Big Mac`, | |
}; | |
const brettsBreakfast2_2: Burger2 = { | |
brand: `BigKahuna`, | |
name: `Le Big Mac`, // Mother… | |
}; | |
// 3. Tagged union | |
type Burger = | |
| { | |
brand: `McDonalds`; | |
name: `Le Big Mac` | `1/4 pounder` | `Cheese Royal`; | |
} | |
| { | |
brand: `BigKahuna`; | |
name: number; | |
}; | |
const brettsBreakfast3_1: Burger = { | |
brand: `McDonalds`, | |
name: `Le Big Mac`, // Fine | |
}; | |
const brettsBreakfast3_2: Burger = { | |
brand: `BigKahuna`, | |
name: `Le Big Mac`, // Caught | |
}; | |
const prepare = (burger: Burger) => { | |
if (burger.brand === `McDonalds`) return mcPrepare(burger); | |
if (burger.brand === `BigKahuna`) return bkPrepare(burger); | |
return burger.brand; // Unreachable | |
}; | |
// 4. One from the union | |
const mcPrepare = (burger: McBurger) => burger.name; | |
// How to define McBurger? | |
type McBurger = Extract<Burger, { brand: `McDonalds` }>; | |
// 5. Favorites at each brand | |
const brettsFavorites: Favorites = { | |
McDonalds: `Le Big Mac`, | |
BigKahuna: 17, | |
}; | |
// How to define Favorites in sync with Burger? | |
type Favorites1 = { | |
[B: string]: Burger[`name`]; | |
}; | |
const brettsFavorites1: Favorites1 = { | |
BigKahuna: 17, | |
// Mother… | |
}; | |
// 6. Intoducing mapped types | |
type Favorites2 = { | |
[B in Burger[`brand`]]: Burger[`name`]; | |
}; | |
const brettsFavorites2: Favorites2 = { | |
McDonalds: 25, // Mother… | |
BigKahuna: 17, | |
}; | |
type Favorites3 = { | |
[B in Burger[`brand`]]: Extract<Burger, { brand: B }>[`name`]; | |
}; | |
const brettsFavorites3: Favorites3 = { | |
McDonalds: 25, // Caught | |
BigKahuna: 17, | |
}; | |
// 7. Key remapping | |
// (since TS 4.1) | |
type Favorites = { | |
[B in Burger as B[`brand`]]: B[`name`]; | |
}; | |
// Other things you can do | |
type Allow = { | |
[B in Burger[`brand`] as `allow${Uppercase<B>}`]: boolean; | |
}; | |
const allowed: Allow = { | |
allowMCDONALDS: true, | |
allowBIGKAHUNA: false, | |
}; | |
// UTILS | |
export const bkPrepare = (burger: Burger) => burger.name; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment