This translation is adapted from the TypeScript Handbook chapter “Mapped Types”.
Mapping type
Sometimes, a type needs to be based on another type, but you don’t want to make a copy.
Mapping types are generic types that use the PropertyKeys associative type, where PropertyKeys are created by keyof and then iterate over the key name to create a type
type OptionsFlags<Type> = {
[Property in keyof Type]: boolean;
};
type FeatureFlags = {
darkMode: () = > void;
newUserProfile: () = > void;
};
type FeatureOptions = OptionsFlags<FeatureFlags>;
// type FeatureOptions = {
// darkMode: boolean;
// newUserProfile: boolean;
// }
Copy the code
Mapping modifier
There are two additional modifiers that may be used when using mapping types: readonly, which sets the property read-only, and? For setting properties Optional.
You can remove or add these modifiers by prefix – or +. If you don’t write a prefix, you use a + prefix
// Delete the read-only attribute from the property
type CreateMutable<Type> = {
-readonly [Property in keyof Type]: Type[Property];
};
type LockedAccount = {
readonly id: string;
name: string;
};
type UnlockedAccount = CreateMutable<LockedAccount>;
// type UnlockedAccount = {
// id: string;
// name: string;
// }
Copy the code
// Remove optional attributes from properties
type Concrete<Type> = {
[Property inkeyof Type]-? : Type[Property]; };type MaybeUser = {
id: string; name? :string; age? :number;
};
type User = Concrete<MaybeUser>;
// type User = {
// id: string;
// name: string;
// age: number;
// }
Copy the code
throughas
Implement key name remapping
In TypeScript 4.1 and later, you can use as statements in mapping types to implement key name remapping
For example, you can use the template literal type to create a new attribute name based on the previous attribute name:
type Getters<Type> = {
Get ${Capitalize
[Property in keyof Type as `get${Capitalize<string & Property>}`] :() = > Type[Property]
};
interface Person {
name: string;
age: number;
location: string;
}
type LazyPerson = Getters<Person>;
// type LazyPerson = {
// getName: () => string;
// getAge: () => number;
// getLocation: () => string;
// }
Copy the code
You can also use condition types to return never to filter out certain attributes:
// Remove the 'kind' property
type RemoveKindField<Type> = {
// Select all the Circle attributes and discard the kind attribute
[Property in keyof Type as Exclude<Property, "kind">]: Type[Property]
};
interface Circle {
kind: "circle";
radius: number;
}
type KindlessCircle = RemoveKindField<Circle>;
// type KindlessCircle = {
// radius: number;
// }
Copy the code
You may also want to traverse any joint type, is not only a string | number | symbol this type of joint can be any type of joint:
type EventConfig<Events extends { kind: string} > = {// E in Events ---> type E = SquareEvent | CircleEvent
// E in Events as E["kind"] ---> 'square' | 'circle'
[E in Events as E["kind"]] :(event: E) = > void;
}
type SquareEvent = { kind: "square".x: number.y: number };
type CircleEvent = { kind: "circle".radius: number };
type Config = EventConfig<SquareEvent | CircleEvent>
// type Config = {
// square: (event: SquareEvent) => void;
// circle: (event: CircleEvent) => void;
// }
Copy the code
Mapping types can also be used with other functions. For example, this is a mapping type that uses conditional types, returning true or false depending on whether the object has a PII attribute
type ExtractPII<Type> = {
[Property in keyof Type]: Type[Property] extends { pii: true}?true : false;
};
type DBFields = {
id: { format: "incrementing" };
name: { type: string; pii: true };
};
type ObjectsNeedingGDPRDeletion = ExtractPII<DBFields>;
// type ObjectsNeedingGDPRDeletion = {
// id: false;
// name: true;
// }
Copy the code