2
头图

The official documentation of TypeScript has long been updated, but the Chinese documents I can find are still in the older version. Therefore, some new and revised chapters have been translated and sorted out.

This article is translated from the " Mapped Types " chapter in the TypeScript Handbook.

This article does not strictly follow the original translation, but also explains and supplements part of the content.

Mapped Types

Sometimes, one type needs to be based on another type, but you don't want to make a copy. At this time, you can consider using a mapping type.

The mapping type is based on the syntax of the index signature. Let’s review the index signature first:

// 当你需要提前声明属性的类型时
type OnlyBoolsAndHorses = {
  [key: string]: boolean | Horse;
};
 
const conforms: OnlyBoolsAndHorses = {
  del: true,
  rodney: false,
};

The mapping type is PropertyKeys union type, of which PropertyKeys is keyof created through 061d45c4c4067a, and then loops through the key names to create a type:

type OptionsFlags<Type> = {
  [Property in keyof Type]: boolean;
};

In this example, OptionsFlags will traverse Type , and then set to Boolean type.

type FeatureFlags = {
  darkMode: () => void;
  newUserProfile: () => void;
};
 
type FeatureOptions = OptionsFlags<FeatureFlags>;
// type FeatureOptions = {
//    darkMode: boolean;
//    newUserProfile: boolean;
// }

Mapping Modifiers

When using the mapping type, there are two additional modifiers that may be used, one is readonly , which is used to set the attribute read-only, and the other is ? , which is used to set the attribute optional.

You can - or + . If no prefix is written, it is equivalent to using the prefix +

// 删除属性中的只读属性
type CreateMutable<Type> = {
  -readonly [Property in keyof Type]: Type[Property];
};
 
type LockedAccount = {
  readonly id: string;
  readonly name: string;
};
 
type UnlockedAccount = CreateMutable<LockedAccount>;

// type UnlockedAccount = {
//    id: string;
//    name: string;
// }
// 删除属性中的可选属性
type Concrete<Type> = {
  [Property in keyof Type]-?: Type[Property];
};
 
type MaybeUser = {
  id: string;
  name?: string;
  age?: number;
};
 
type User = Concrete<MaybeUser>;
// type User = {
//    id: string;
//    name: string;
//    age: number;
// }

By as achieve remap keys (Key Remapping Via as )

In TypeScript 4.1 and later, you can use the as statement in the mapping type to achieve key name remapping:

type MappedTypeWithNewProperties<Type> = {
    [Properties in keyof Type as NewKeyType]: Type[Properties]
}

For example, you can use " template literal type " to create a new attribute name based on the previous attribute name:

type Getters<Type> = {
    [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;
// }

You can also use the condition type to return a never to filter out certain attributes:

// Remove the 'kind' property
type RemoveKindField<Type> = {
    [Property in keyof Type as Exclude<Property, "kind">]: Type[Property]
};
 
interface Circle {
    kind: "circle";
    radius: number;
}
 
type KindlessCircle = RemoveKindField<Circle>;

// type KindlessCircle = {
//    radius: number;
// }

You can also traverse any union type, not just string | number | symbol , it can be any type of union:

type EventConfig<Events extends { kind: string }> = {
    [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;
// }

Further Exploration

The mapping type can also be used in conjunction with other functions. For example, this is a mapping type that uses a condition type. It will return true or false 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;
// }

TypeScript series

The TypeScript series of articles consists of three parts: official document translation, important and difficult analysis, and practical skills. It covers entry, advanced, and actual combat. It aims to provide you with a systematic learning TS tutorial. The entire series is expected to be about 40 articles. Click here to browse the full series of articles, and suggest to bookmark the site by the way.

WeChat: "mqyqingfeng", add me to the only reader group in Kongyu.

If there are mistakes or not rigorous, please correct me, thank you very much. If you like or have some inspiration, star is welcome, which is also an encouragement to the author.


冴羽
9.3k 声望6.3k 粉丝