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

throughasImplement 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