Diving into TypeScript Fundamentals: Generics, keyof, and typeof

The second lesson of this holiday ☃️ TypeScript challenge is here. In our previous post, which kicked off this series, we delved into the basics of union types. It was a quick and straightforward introduction to set the stage.

Now, brace yourself for today's challenge and lesson—it's a bit more broad, covering various foundational TypeScript concepts. I truly think you are going to like it. Let's dive right in!

Day two - Dec 2nd

I strongly encourage you to try the first challenge on your own before reading the solution here.

Challenge:

Christmas Cookie Inventory

The challenge

Phew! Yesterday's tactic worked. Santa got down-and-dirty with the elves on the factory floor and they seem to have stopped planning their strike.

With one small exception.. Unfortunately, two pesky elves (Jingle and Jangle) have realized that the 300 year stock options vesting cliff that Santa put into the elves' contract isn't quite typical. Jingle and Jangle already joined forces with Hermey (who has nothing to lose because he'd rather be a dentist than make toys) and they're beginning to cause a fuss.

Santa noticed that a lot of this discussion is happening during cookie inventory. Help Santa speed up the process so these conversations are cut short.

Take a look at the cookieInventory variable in the tests. Your goal is to update CookieSurveyInput so that it will return a union of all of the names of the various different cookies.

Good luck! As Santa always says: "your hard work will pay off eventually, just be patient".

The code to complete

type CookieSurveyInput = unknown;

The tests

import { Expect, Equal } from "type-testing";

const cookieInventory = {
    chocolate: 1,
    sugar: 20,
    gingerBread: 10,
    peanutButter: 30,
    snickeDoodle: 73,
}
type test_cookies_actual = CookieSurveyInput<typeof cookieInventory>;
//   ^?
type test_cookies_expected = "chocolate" | "sugar" | "gingerBread" | "peanutButter" | "snickeDoodle";
type test_cookies = Expect<
    Equal<
        test_cookies_actual,
        test_cookies_expected
    >
>;

The solution

This challenge is asking for a type that, given an object type representing a cookie inventory, returns a union of all the names of the cookies in the inventory.

Here’s what that would look like:

type CookieSurveyInput<T> = keyof T;

Let’s break the solution down:

  • keyof T is a TypeScript utility type that returns a union type representing the names of all the properties (keys) of the object type T.

  • CookieSurveyInput<T> is a generic type that takes an object type T as its parameter and returns the union of keys of that type.

Now, let's look at the usage and tests:

const cookieInventory = {
  chocolate: 1,
  sugar: 20,
  gingerBread: 10,
  peanutButter: 30,
  snickeDoodle: 73,
};

type test_cookies_actual = CookieSurveyInput<typeof cookieInventory>;
// This will be a union type of "chocolate" | "sugar" | "gingerBread" | "peanutButter" | "snickeDoodle"

Another key part of the solution is that we are leveraging typeof to help us get the type of the cookieInventory variable. The line typeof cookieInventory translates to this:

type InventoryType = {
  chocolate: number;
  sugar: number;
  gingerBread: number;
  peanutButter: number;
  snickeDoodle: number;
};

typeof is a TypeScript operator that allows you to extract the type of a variable or expression.

Why use a generic type?

Generic types open the door to flexibility and reusability. Imagine you have different kinds of cookie inventories:

const xmasCookiesInventory = {
  mintChocolate: 20,
  gingerBread: 10,
};

const regularCookiesInventory = {
  raisin: 12, 
  chocolate: 1,
};

Then you want to define a union type for each different kind of inventory:

type CookieSurveyInput<T> = keyof T;

type xmas_cookie = CookieSurveyInput<typeof xmasCookiesInventory>;
// This will be a union type of "mintChocolate" | "gingerBread"

type regular_cookie = CookieSurveyInput<typeof regularCookiesInventory>;
// This will be a union type of "raisin" | "chocolate"

Using a generic type allows you to reuse the same CookieSurveyInput for every inventory. I like how explicit and readable all the type definitions are with this approach.

Removing the Generic Type

If you know for sure that you'll stick with a particular object (just one cookie inventory), you can simplify things by defining a "fixed" type directly from that object:

type CookieSurveyInput = keyof typeof cookieInventory;

Essentially, you're achieving the same result as before in a single step by skipping the generic type.

Both solutions are valid, and the choice between them depends on the context and whether you need a more generic solution or a specific one tailored to a particular object.

Summary of what we learned from TypeScript:

  • Generic types

  • keyof utility

  • typeof operator

I hope you enjoyed today’s TypeScript challenge. We are one more day closer to Christmas 🎄🦌.

See you tomorrow in the next one!