🗺️ Mapped Types: The Magic Copy Machine
Imagine you have a magical photocopier. But this isn’t an ordinary copier—it can change things while copying! Want all your homework answers to become questions? This copier can do that. Want to make everything optional? Done!
🎯 What Are Mapped Types?
Think of a mapped type like a recipe transformer. You give it one recipe, and it creates a new recipe by changing every ingredient in a specific way.
Simple Example: The Cookie Box
Imagine you have a box of cookies:
type CookieBox = {
chocolate: string;
vanilla: string;
strawberry: string;
};
Now, you want to know: “Did I eat each cookie?” You need a new box that tracks if each cookie was eaten:
type AteTheCookie = {
[Cookie in keyof CookieBox]: boolean;
};
// Result:
// {
// chocolate: boolean;
// vanilla: boolean;
// strawberry: boolean;
// }
What happened? The mapped type went through EACH cookie and changed its type from string to boolean!
🔑 The Magic Formula
type NewType = {
[Key in keyof OriginalType]: NewValueType;
};
Let’s break this down like a sandwich:
graph TD A["[Key in keyof T]"] --> B["Go through each key"] B --> C["For EACH key..."] C --> D["Create a new property"] D --> E["With the new type you specify"]
| Part | What It Does |
|---|---|
[Key in keyof T] |
Loop through all keys in T |
: |
Assign a new type |
NewType |
What each value becomes |
🎚️ Mapping Modifiers: Adding Superpowers
Modifiers are like magic words that change properties. There are two main ones:
1. The ? Modifier (Optional)
Makes properties optional (you don’t have to fill them in).
type Person = {
name: string;
age: number;
};
// Make everything optional
type MaybeAPerson = {
[Key in keyof Person]?: Person[Key];
};
// Result:
// {
// name?: string;
// age?: number;
// }
2. The readonly Modifier (Read-Only)
Makes properties unchangeable (like writing with permanent marker).
type LockedPerson = {
readonly [Key in keyof Person]: Person[Key];
};
// Result:
// {
// readonly name: string;
// readonly age: number;
// }
➖ Removing Modifiers: The Minus Sign
What if you want to REMOVE these modifiers? Use the minus sign -!
Remove Optional (-?)
type RequiredPerson = {
[Key in keyof MaybeAPerson]-?: MaybeAPerson[Key];
};
// Takes optional properties
// and makes them REQUIRED again!
Remove Readonly (-readonly)
type UnlockedPerson = {
-readonly [Key in keyof LockedPerson]: LockedPerson[Key];
};
// Takes readonly properties
// and makes them CHANGEABLE again!
🎨 Adding Modifiers: The Plus Sign
You can also use + to explicitly ADD modifiers (though it’s optional):
// These are the same:
type A = { [K in keyof T]?: T[K] };
type B = { [K in keyof T]+?: T[K] };
Think of + as saying “YES, definitely add this!”
🏷️ Key Remapping: Rename While You Copy
This is where things get REALLY cool! You can rename keys as you copy them.
The as Keyword
type Getters = {
[Key in keyof Person as `get${Capitalize<Key>}`]:
() => Person[Key];
};
// Result:
// {
// getName: () => string;
// getAge: () => number;
// }
What happened?
namebecamegetNameagebecamegetAge
graph TD A["Original: name, age"] --> B["Add &#39;get&#39; prefix"] B --> C["Capitalize first letter"] C --> D["getName, getAge"]
🗑️ Filtering Keys: Remove What You Don’t Want
Use never to SKIP certain keys:
type RemoveAge = {
[Key in keyof Person as
Key extends 'age' ? never : Key
]: Person[Key];
};
// Result:
// {
// name: string;
// // age is GONE!
// }
It’s like saying: “Copy everything… EXCEPT age!”
🎁 Built-in Helpers (TypeScript Gives You These!)
TypeScript already has these mapped types ready for you:
| Helper | What It Does |
|---|---|
Partial<T> |
Makes all properties optional |
Required<T> |
Makes all properties required |
Readonly<T> |
Makes all properties readonly |
Pick<T, K> |
Picks only certain properties |
Omit<T, K> |
Removes certain properties |
Record<K, T> |
Creates a type with keys K and values T |
How They Work Inside
// Partial (built-in)
type Partial<T> = {
[K in keyof T]?: T[K];
};
// Required (built-in)
type Required<T> = {
[K in keyof T]-?: T[K];
};
// Readonly (built-in)
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
🧪 Real-World Example: Form States
Imagine you’re building a form:
type UserForm = {
username: string;
email: string;
password: string;
};
// For the "touched" state (which fields did user click?)
type TouchedFields = {
[K in keyof UserForm]: boolean;
};
// For error messages
type FormErrors = {
[K in keyof UserForm]?: string;
};
// For loading states per field
type LoadingStates = {
[K in keyof UserForm as `${K}Loading`]: boolean;
};
// Result: { usernameLoading: boolean; ... }
🌟 The Big Picture
graph TD A["Original Type"] --> B["Mapped Type"] B --> C["Loop through keys"] C --> D{"What to do?"} D --> E["Change value type"] D --> F["Add/Remove modifiers"] D --> G["Rename keys"] E --> H["New Type!"] F --> H G --> H
✨ Summary: Your Mapped Types Toolkit
| Concept | Syntax | Effect |
|---|---|---|
| Basic Mapping | [K in keyof T] |
Copy structure |
| Change Type | : NewType |
Transform values |
| Add Optional | ? or +? |
Properties optional |
| Remove Optional | -? |
Properties required |
| Add Readonly | readonly or +readonly |
Can’t change |
| Remove Readonly | -readonly |
Can change again |
| Rename Keys | as NewKey |
New property names |
| Filter Keys | as ... ? never : Key |
Remove properties |
🚀 You Did It!
You now understand mapped types—the powerful feature that lets you:
- Copy and transform entire types at once
- Add or remove optional/readonly modifiers
- Rename keys while copying
- Filter out properties you don’t want
It’s like having a magical copy machine that can do ANYTHING! 🎉
Remember: Mapped types help you avoid repeating yourself. Instead of writing the same type structure over and over, you write it ONCE and let TypeScript do the transforming!
