├── LICENSE ├── README.md ├── cheat-sheet.fig ├── cheat-sheet.md ├── cheat-sheet.pdf ├── cheat-sheet.png ├── images ├── error-message.png ├── intersections-venn.png ├── sets-venn.png └── union-venn.png └── small.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Carl Riis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Magic TypeScript

2 |

A cheat-sheet of TypeScript’s most important/magic features

3 | 4 |

Read the blog post about it here

5 | 6 | [Image](https://carltheperson.com/images/magic-typescript/magic-typescript.png) 7 | 8 | # The cheat sheet 9 | 10 | [Click here to view it in full size](https://carltheperson.com/images/magic-typescript/magic-typescript.png) 11 | 12 | # Document version 13 | 14 | If you prefer to read the cheat sheet in a simple document, you can find that [here](cheat-sheet.md). 15 | 16 | There is a PDF version [here](cheat-sheet.pdf). 17 | 18 | # Source 19 | 20 | [This is the Figma file.](cheat-sheet.fig) 21 | 22 | # Issues 23 | 24 | I may have made a mistake or two. If you find one, feel free to open an issue, and I will fix it. 25 | 26 | # License 27 | 28 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 29 | -------------------------------------------------------------------------------- /cheat-sheet.fig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/magic-typescript/f6ee377c1023a7eaacf011d7a7e9d2a1fdc0e8c5/cheat-sheet.fig -------------------------------------------------------------------------------- /cheat-sheet.md: -------------------------------------------------------------------------------- 1 | # Magic TypeScript 2 | 3 | **A cheat sheet of TypeScript’s most important/magic features** 4 | 5 | # Subsets 6 | 7 | This is the most important concept to understand about TypeScript. Think of all types as sets of values. 8 | 9 | ```ts 10 | never; // Special type meaning empty set 11 | ("dog"); // Unit type. A set with only the value "dog" 12 | "dog" | "cat"; // Union type. A set with only the values "dog" and "cat" 13 | string; // A set containing every string value 14 | number; // A set containing every number 15 | any; // Special type which contains every value in its set 16 | ``` 17 | 18 | You can use one type in place of another as long as its set overlaps fully. In other words: 19 | 20 | ![Untitled](images/sets-venn.png) 21 | 22 | Examples: 23 | 24 | ```ts 25 | type Type1 = "apple" | "orange"; 26 | type Type2 = "apple"; 27 | type Type3 = "apple" | "banana"; 28 | 29 | const func = (food: Type1) => { 30 | /* ... */ 31 | }; 32 | // This function will let you pass in type Type1 and Type2 but not Type3 33 | ``` 34 | 35 | ```ts 36 | interface Pet { 37 | name: string; 38 | } 39 | 40 | interface Dog { 41 | name: string; 42 | favouriteToy: string; 43 | furType: "curly" | "flat"; 44 | } 45 | // Any function needing Pet as a parameter will happily also accept Dog 46 | ``` 47 | 48 | ```ts 49 | const fetchTranslation = (language: "English" | "Spanish" | "German") => { 50 | /* ... */ 51 | }; 52 | const language = getRandomLanguage(); // Type is string 53 | fetchTranslation(language); 54 | // This does NOT work since 'string' could be a bunch of values that 55 | // are not "English", "Spanish", or "German" 56 | ``` 57 | 58 | ### Error message translation 59 | 60 | ![error-message.png](images/error-message.png) 61 | 62 | This means that the set of TypeA is not contained inside the set of TypeB. 63 | 64 | # Inference 65 | 66 | If you don't specify a type TypeScript will make a guess and assign one. 67 | 68 | ```ts 69 | let name = "Carl"; // Type is string 70 | ``` 71 | 72 | Using _const_ makes TypeScript infer a different type. This is because _const_ makes the variable immutable. 73 | 74 | ```ts 75 | const name = "Carl"; // Type is "Carl" 76 | ``` 77 | 78 | ### as const 79 | 80 | TypeScript normally infers a pretty wide type for objects, but you can force it to narrow it with _as const._ 81 | 82 | ```ts 83 | const person = { name: "Carl" }; // Type is { name: string } 84 | const person = { name: "Carl" } as const; // Type is { name: "Carl" } 85 | ``` 86 | 87 | ### Narrowing with conditions 88 | 89 | TypeScript can narrow a type depending on the context. 90 | 91 | ```ts 92 | const user = getUserFromId("123"); // Type is User | null 93 | if (user) { 94 | // Type of user is User 95 | } else { 96 | // Type of user is null 97 | } 98 | ``` 99 | 100 | # Type guards 101 | 102 | You can help the type system infer types with type guards. They allow you to define your own checks for types. 103 | 104 | ```ts 105 | const isCat = (pet: Cat | Dog): pet is Cat => { 106 | return pet.latestMessage.includes("meow"); 107 | }; 108 | 109 | const pet = getRandomPet(); // Type is Cat | Dog 110 | if (isCat(pet)) { 111 | // Type is Cat 112 | } else { 113 | // Type is Dog 114 | } 115 | ``` 116 | 117 | # Unions 118 | 119 | A union type is a type that has the possibility to be other types. When working with unions of objects, you can only access properties that both types have in common. 120 | 121 | ![union-venn.png](images/union-venn.png) 122 | 123 | ```ts 124 | interface Cat { 125 | isSleeping: boolean; 126 | isMeowing: boolean; 127 | } 128 | 129 | interface Dog { 130 | isSleeping: boolean; 131 | isPlayingFetch: boolean; 132 | } 133 | 134 | // Same as { isSleeping: boolean } 135 | type Pet = Cat | Dog; 136 | 137 | const dog: Pet = getRandomDog(); // OK 138 | ``` 139 | 140 | # Intersections 141 | 142 | Intersections are related to union types, but they combine everything into one set. 143 | 144 | ![intersections-venn.png](images/intersections-venn.png) 145 | 146 | ```ts 147 | interface Person { 148 | name: string; 149 | age: number; 150 | } 151 | 152 | interface LocationData { 153 | country: string; 154 | address: string; 155 | } 156 | 157 | type PersonWithLocation = Person & LocationData; // Same as: 158 | // { name: string, age: number, country: string, address: string } 159 | 160 | // TS Error: Type Person is missing: country, address 161 | const person: PersonWithLocation = getRandomPerson(); 162 | ``` 163 | 164 | # Enums 165 | 166 | Enums let you define a set of named constants. They act very similar to union types. 167 | 168 | ```ts 169 | enum SeverityCode { 170 | CRITICAL = 0 171 | WARNING = 200 172 | NORMAL = 1000 173 | } 174 | 175 | if (getSeverity() === SeverityCode.CRITICAL) { 176 | // Panic! 177 | } 178 | ``` 179 | 180 | This is a very broad type that will let you use keys you haven't defined. You can define the allowed set for the keys with the _in_ keyword. It will even warn you if you don't define a value for all the keys. The global utility type _Record_ uses this. 181 | 182 | ```ts 183 | const ages: { [name in "Ernest" | "Chester"]: number } = { 184 | Ernest: 36, 185 | Chester: 21, 186 | }; 187 | ages["Ernest"]; // 36 188 | ages["Harvey"]; // TS Error: Property 'Harvey' does not exist ... 189 | 190 | // Exact same thing as above 191 | const ages: Record<"Ernest" | "Chester", number> = { 192 | Ernest: 36, 193 | Chester: 21, 194 | }; 195 | ``` 196 | 197 | # Index signatures (key/value) 198 | 199 | Index signatures allow you to define types for key value pairs. 200 | 201 | ```ts 202 | const ages: { [name: string]: number } = { 203 | Ernest: 36, 204 | Chester: 21, 205 | }; 206 | ages["Ernest"]; // 36 207 | ``` 208 | 209 | This is a very broad type that will let you use keys you haven't defined. You can define the allowed set for the keys with the _in_ keyword. It will even warn you if you don't define a value for all the keys. The global utility type _Record_ uses this. 210 | 211 | ```ts 212 | const ages: { [name in "Ernest" | "Chester"]: number } = { 213 | Ernest: 36, 214 | Chester: 21, 215 | }; 216 | ages["Ernest"]; // 36 217 | ages["Harvey"]; // TS Error: Property 'Harvey' does not exist ... 218 | 219 | // Exact same thing as above 220 | const ages: Record<"Ernest" | "Chester", number> = { 221 | Ernest: 36, 222 | Chester: 21, 223 | }; 224 | ``` 225 | 226 | ### Mapping enums 227 | 228 | ```ts 229 | const colors: Record = { 230 | [SeverityCode.CRITICAL]: "Red", 231 | [SeverityCode.WARNING]: "yellow", 232 | [SeverityCode.NORMAL]: "green", 233 | }; 234 | 235 | colors[SeverityCode.WARNING]; // "yellow" 236 | colors[200]; // "yellow" 237 | ``` 238 | 239 | # Generics 240 | 241 | Generics can be used to make types more flexible. They allow shared behavior for different types. 242 | 243 | ```ts 244 | interface HTTPResponse { 245 | status: number; 246 | data: T; 247 | } 248 | 249 | interface User { 250 | name: string; 251 | } 252 | 253 | // Type is { status: number, data: { name: string} } 254 | type UserHTTPResponse = HTTPResponse; 255 | 256 | // Type is { status: number, data: string } 257 | type StringHTTPResponse = HTTPResponse; 258 | ``` 259 | 260 | ### extends 261 | 262 | The extends keyword can be used to limit which types are allowed to be used generically. 263 | 264 | TypeA extends TypeB means that TypeA's set of values is contained within TypeB's. 265 | 266 | ```ts 267 | const getUser = (id: T): { id: T; name: string } => { 268 | return fetchUser(id); 269 | }; 270 | 271 | // TS Error: Argument of type 'boolean' is not assignable to string | number 272 | getUser(true); 273 | 274 | getUser(123); // Return type is { id: number, name: string } 275 | getUser("abc"); // Return type is { id: string, name: string } 276 | ``` 277 | 278 | # Conditional types 279 | 280 | TypeScript supports conditional types inspired by JavaScript's ternary operator. You use _extends_ as an assertion. 281 | 282 | ```ts 283 | type BooleanFromString = T extends "true" ? true : false; 284 | 285 | type Boolean1 = BooleanFromString<"true">; // Type is true 286 | type Boolean2 = BooleanFromString<"false">; // Type is false 287 | type Boolean3 = BooleanFromString<"true" | "false">; // Type is boolean 288 | ``` 289 | 290 | You can also use conditional types for the return type of a function. 291 | 292 | # typeof 293 | 294 | The _typeof_ keyword acts very differently depending on if it's in a JavaScript context or a TypeScript context. Reason being that TypeScript types don't exist when code is running. 295 | 296 | ```ts 297 | const pet = getRandomPet(); 298 | 299 | // TypeScript context 300 | // (Line does not exist when code is running) 301 | type PetType = typeof pet; // Type is Cat | Dog 302 | 303 | // JavaScript context 304 | console.log(typeof pet); // Prints "object" 305 | ``` 306 | 307 | The only types that exist at runtime are: `bigint, boolean, function, number, object, string, symbol, undefined.` 308 | 309 | # keyof 310 | 311 | _keyof_ allows you to convert an object type into a union of its keys. 312 | 313 | ```ts 314 | interface User { 315 | name: string; 316 | age: number; 317 | } 318 | 319 | type UserKeys = keyof User; // Type is same as "name" | "age" 320 | ``` 321 | 322 | ```ts 323 | const select = (data: T, key: keyof T) => { 324 | return data[key]; 325 | }; 326 | 327 | const user = { name: "Alice", age: 24 }; 328 | 329 | select(user, "age"); // Type is number 330 | select(user, "height"); // TS Error: "height" is not assignable to "age" | "name" 331 | ``` 332 | 333 | # Template literal types 334 | 335 | ```ts 336 | type Drink = "tea" | "coffee"; 337 | type Size = "S" | "M" | "L"; 338 | 339 | // Type is "S-tea" | "S-coffee" | "M-tea" | "M-coffee" | "L-tea" | "L-coffee" 340 | type DrinkVariant = `${Size}-${Drink}`; 341 | ``` 342 | 343 | Idea: Create an Index signature to map DrinkVariants to prices 344 | 345 | # Tuples 346 | 347 | ```ts 348 | type Coordinates = [number, number]; 349 | 350 | const c1: Coordinates = [5, 12]; 351 | const c2: Coordinates = [4]; // TS error: Has 1 element but requires 2 352 | const c3: Coordinates = [7, "9"]; // TS error: string is not assignable to type number 353 | ``` 354 | 355 | ### Spread tuples 356 | 357 | ```ts 358 | // A person can be called multiple names but always has at least one 359 | type Names = [string, ...string[]]; 360 | 361 | const p1Names: Names = ["Albert", "Bert", "Al"]; 362 | const p2Names: Names = []; // TS error: Has 0 elements but requires 1 363 | ``` 364 | 365 | # readonly 366 | 367 | You can use the _readonly_ modifier to prevent values from being mutated in unexpected ways. 368 | 369 | ```ts 370 | const printArray = (array: readonly number[]) => { 371 | array[0] = 100; // TS error: only reading is permitted 372 | }; 373 | 374 | const array = [1, 2, 3]; 375 | // We know for sure the function won't mutate our array 376 | printArray(array); 377 | ``` 378 | 379 | # Useful global utility types 380 | 381 | **Partial** 382 | 383 | ```ts 384 | // Make all properties in T optional 385 | type Partial = { 386 | [P in keyof T]?: T[P]; 387 | }; 388 | 389 | type Example = Partial<{ name: string; age: number }>; 390 | // { name?: string | undefined, age?: number | undefined } 391 | ``` 392 | 393 | **Required** 394 | 395 | ```ts 396 | // Make all properties in T required 397 | type Required = { 398 | [P in keyof T]-?: T[P]; 399 | }; 400 | 401 | type Example = Required<{ name?: string; age?: number }>; 402 | // { name: string, age: number } 403 | ``` 404 | 405 | **Pick** 406 | 407 | ```ts 408 | // Keep only the properties in T which are in the union K 409 | type Pick = { 410 | [P in K]: T[P]; 411 | }; 412 | 413 | type Example = Pick<{ id: string; name: string; age: number }, "id" | "name">; 414 | // { id: string, name: string } 415 | ``` 416 | 417 | **Extract** 418 | 419 | ```ts 420 | // Extract from union T what is also in union U 421 | type Extract = T extends U ? T : never; 422 | 423 | type Example = Extract<"a" | "b" | "c", "a" | "c">; 424 | // "a" | "c" 425 | ``` 426 | 427 | **Exclude** 428 | 429 | ```ts 430 | // Exclude from union T what is also in union U 431 | type Exclude = T extends U ? never : T; 432 | 433 | type Example = Exclude<"a" | "b" | "c", "a" | "c">; 434 | // "b" 435 | ``` 436 | 437 | **Omit** 438 | 439 | ```ts 440 | // Keep only the properties in T which share no keys with union K 441 | type Omit = Pick>; 442 | 443 | type Example = Omit<{ id: string; name: string; age: number }, "id" | "age">; 444 | // { name: string } 445 | ``` 446 | 447 | **NonNullable** 448 | 449 | ```ts 450 | // Exclude null and undefined from T 451 | type NonNullable = T extends null | undefined ? never : T; 452 | 453 | type Example = NonNullable; 454 | // string 455 | ``` 456 | 457 | **Others** 458 | 459 | Other global utility types not listed include: 460 | 461 | ``` 462 | Parameters, ConstructorParameters, ReturnType, InstanceType, Uppercase, Lowercase, Capitalize, Uncapitalize, ThisType, Readonly, ArrayLike, Awaited, Promise, PromiseLike 463 | ``` 464 | -------------------------------------------------------------------------------- /cheat-sheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/magic-typescript/f6ee377c1023a7eaacf011d7a7e9d2a1fdc0e8c5/cheat-sheet.pdf -------------------------------------------------------------------------------- /cheat-sheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/magic-typescript/f6ee377c1023a7eaacf011d7a7e9d2a1fdc0e8c5/cheat-sheet.png -------------------------------------------------------------------------------- /images/error-message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/magic-typescript/f6ee377c1023a7eaacf011d7a7e9d2a1fdc0e8c5/images/error-message.png -------------------------------------------------------------------------------- /images/intersections-venn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/magic-typescript/f6ee377c1023a7eaacf011d7a7e9d2a1fdc0e8c5/images/intersections-venn.png -------------------------------------------------------------------------------- /images/sets-venn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/magic-typescript/f6ee377c1023a7eaacf011d7a7e9d2a1fdc0e8c5/images/sets-venn.png -------------------------------------------------------------------------------- /images/union-venn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/magic-typescript/f6ee377c1023a7eaacf011d7a7e9d2a1fdc0e8c5/images/union-venn.png -------------------------------------------------------------------------------- /small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carltheperson/magic-typescript/f6ee377c1023a7eaacf011d7a7e9d2a1fdc0e8c5/small.png --------------------------------------------------------------------------------