TypeScript Mapped Types
Mapped types let you transform every property of an existing type into a new type using a single, reusable declaration.
What Are Mapped Types?
A mapped type walks over the keys of another type using the `[K in keyof T]` syntax and produces a new type with the same keys but transformed values. This lets you avoid repeating the same shape twice when you only need a variation of an existing interface, such as making every field optional or turning every value into a boolean flag.
A Basic Mapped Type
type Feature = {
name: string;
enabled: boolean;
votes: number;
};
type FeatureFlags<T> = {
[K in keyof T]: boolean;
};
const flags: FeatureFlags<Feature> = {
name: true,
enabled: true,
votes: false,
};
console.log(flags);Modifiers: readonly and optional
Mapped types can add or strip the `readonly` and `?` modifiers using the `+` and `-` prefixes. `-readonly` removes read-only-ness and `-?` removes optionality, which is useful when you need a fully mutable, fully required version of a type that was declared the opposite way.
Adding and Removing Modifiers
interface Config {
readonly host: string;
port?: number;
}
// Strip readonly and make every property required
type Mutable<T> = {
-readonly [K in keyof T]-?: T[K];
};
type MutableConfig = Mutable<Config>;
const cfg: MutableConfig = { host: "localhost", port: 8080 };
cfg.host = "127.0.0.1"; // allowed now that readonly is removed
console.log(cfg);Remapping Keys with as
Since TypeScript 4.1, mapped types can rename each key with an `as` clause, typically combined with template literal types. This is how libraries generate getter/setter names, event handler names, or prefixed variants of an existing type without hand-writing every property.
Remapping Keys
interface Person {
name: string;
age: number;
}
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
function makeGetters<T extends object>(obj: T): Getters<T> {
const result = {} as Getters<T>;
for (const key of Object.keys(obj) as (keyof T)[]) {
const name = `get${String(key).charAt(0).toUpperCase()}${String(key).slice(1)}`;
(result as Record<string, () => unknown>)[name] = () => obj[key];
}
return result;
}
const person: Person = { name: "Ada", age: 34 };
const getters = makeGetters(person);
console.log(getters.getName(), getters.getAge());- Partial<T> - makes every property optional
- Required<T> - makes every property required
- Readonly<T> - makes every property readonly
- Record<K, T> - builds an object type from a key union and a value type
- Pick<T, K> - keeps only the chosen keys
- Omit<T, K> - drops the chosen keys
Exercise: TypeScript Mapped Types
What does a mapped type such as `{ [K in keyof T]: T[K] }` fundamentally do?