14.7 Building Custom Utility Types with Mapped Types

Right, so you’ve grasped the basics of mapped types—iterating over keys, slapping on a modifier or two. But that’s like learning how to hold a scalpel. The real fun begins when you start building your own utility types, the ones that solve your specific problems. This is where you move from reading the map to drawing your own. Let’s start by building something deceptively simple but incredibly powerful. You know how Partial<T> makes everything optional? What if you need the exact opposite? A type where every property is required, even the ones that were originally optional? The existing Required<T> type does this, but let’s build our own to see the gears turn.

14.6 Combining Mapped Types with Conditional Types

Now we get to the good stuff. Combining mapped types with conditional types is where you stop just rearranging the furniture and start knocking down walls to redesign the whole house. This is the power tool that lets you surgically transform one type into another based on the characteristics of its properties, not just their names. It’s how you move from “make everything optional” to “make only the properties of a certain shape optional.”

14.5 Key Remapping with as Clauses

Alright, let’s talk about taking the keys you get from your mapped type and giving them a proper makeover. You’ve seen how to transform property values, but what if the key names themselves are the problem? That’s where key remapping with the as clause comes in. It’s the feature that takes mapped types from “usefully transformative” to “downright magical.” The syntax looks a bit alien at first, but you’ll get used to it. Instead of just [K in KeyType]: ValueType, you write [K in KeyType as NewKeyType]: ValueType. The as NewKeyType part is where you remap the key. The magic is that NewKeyType can be a string literal type that’s derived from the original key K. This is how you escape the confines of the original object’s key set.

14.4 Modifiers in Mapped Types: readonly and ? with + and -

Right, let’s talk about making your mapped types less… one-note. You’ve learned how to create a new type by iterating over another one, but so far, we’ve just been copying properties verbatim. That’s like having a remote control with only a power button—functional, but you’re missing all the good channels. The readonly and ? modifiers are your volume and channel buttons. And the real magic? You can add or remove these modifiers on the fly using + and -. This is where you stop just describing types and start sculpting them.

14.3 Mapping Over Union Types

Now, let’s get to the fun part: what happens when you throw a mapped type at a union? The short answer is magic. The slightly longer answer is that TypeScript performs a distributive operation that feels like it’s doing your laundry and folding it for you. It’s one of the language’s most elegant features, and once you understand it, you’ll start seeing patterns everywhere. Here’s the gist: when you map over a union type, the operation distributes over each member of the union. It’s as if you applied the mapped type to each individual type in the union and then smooshed the results back together into a new union.

14.2 keyof: Getting the Keys of a Type

Right, let’s talk about keyof. It’s one of those TypeScript fundamentals that seems trivial until you realize it’s the secret ingredient in almost every powerful type operation you’ll ever do. It’s the crowbar that lets you pry open a type, extract its keys, and do something useful with them. Think of any object type. It’s just a bag of properties, each with a name (a key) and a value. keyof is how you get a union type of all those keys. It’s like asking TypeScript, “Hey, for this type Person, what are the valid strings I could use to index into an object of this type?”

14.1 Mapped Types: Transforming Every Property of a Type

Let’s talk about Mapped Types, which are essentially your way of telling TypeScript, “Hey, I want to take this existing type and systematically transform every single property in it.” It’s like a factory assembly line for your types. You provide the blueprint (the original type) and the modification instructions (the transformation), and TypeScript cranks out a brand new, transformed type. This is where you move from merely describing your data to actively shaping it with code.

— joke —

...