映射类型

有时候我们不想重复编写代码,这时候就需要基于某个类型创建出另一个类型。

索引签名用于为那些没有提前声明的属性去声明类型,而映射类型是基于索引签名的语法构建的。

  1. type OnlyBoolsAndHorses = {
  2. [key: string]: boolean | Horse;
  3. };
  4. const conforms: OnlyBoolsAndHorses = {
  5. del: true,
  6. rodney: false,
  7. };

映射类型也是一种泛型类型,它使用 PropertyKey(属性键)的联合类型(通常通过 keyof 创建)去遍历所有的键,从而创建一个新的类型:

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

在这个例子中,OptionsFlags 会接受类型 Type 的所有属性,并将它们的值改为布尔值。

  1. type FeatureFlags = {
  2. darkMode: () => void;
  3. newUserProfile: () => void;
  4. };
  5. type FeatureOptions = OptionsFlags<FeatureFlags>;
  6. ^
  7. /*
  8. type FeatureOptions = {
  9. darkMode: boolean;
  10. newUserProfile: boolean;
  11. }
  12. */

映射修饰符

在映射的时候还有两个附加的修饰符可供使用,也就是 readonly?,它们分别用于声明属性的只读性和可选性。

要移除或者添加修饰符,只需要给修饰符添加前缀 - 或者 + 即可。如果没有添加前缀,则默认使用 +

  1. // 移除类型中属性的只读性
  2. type CreateMutable<Type> = {
  3. -readonly [Property in keyof Type]: Type[Property];
  4. };
  5. type LockedAccount = {
  6. readonly id: string;
  7. readonly name: string;
  8. };
  9. type UnlockedAccount = CreateMutable<LockedAccount>;
  10. ^
  11. /*
  12. type UnlockedAccount = {
  13. id: string;
  14. name: string;
  15. }
  16. */
  1. // 移除类型中属性的可选性
  2. type Concrete<Type> = {
  3. [Property in keyof Type]-?: Type[Property];
  4. }
  5. type MaybeUser = {
  6. id: string;
  7. name?: string;
  8. age?: number;
  9. };
  10. type User = Concrete<MaybeUser>;
  11. ^
  12. /*
  13. type User = {
  14. id: string;
  15. name: string;
  16. age: number;
  17. }
  18. */

通过 as 实现键的重新映射

在 TypeScript4.1 或者更高的版本中,你可以在映射类型中使用 as 子句实现键的重新映射:

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

你可以使用诸如模板字面量类型这样的特性从原来的属性名中去创建新的属性名:

  1. type Getters<Type> = {
  2. [Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
  3. };
  4. interface Person {
  5. name: string;
  6. age: number;
  7. location: string;
  8. }
  9. type LazyPerson = Getters<Person>;
  10. ^
  11. /*
  12. type LazyPerson = {
  13. getName: () => string;
  14. getAge: () => number;
  15. getLocation: () => string;
  16. }
  17. */

你可以通过条件类型产生 never 类型,从而过滤掉某些键:

  1. // 移除 kind 属性
  2. type Exclude<T,U> = T extends U ? never : T
  3. type RemoveKindField<Type> = {
  4. [Property in keyof Type as Exclude<Property,'kind'>]: Type[Property]
  5. };
  6. interface Circle {
  7. kind: 'circle';
  8. radius: number;
  9. }
  10. type KindlessCircle = RemoveKindField<Circle>;
  11. ^
  12. /*
  13. type KindlessCircle = {
  14. radius: number;
  15. }
  16. */

不仅仅是 string | number | symbol 这样的联合类型,任意的联合类型都可以映射:

  1. type EventConfig<Events extends { kind: string }> = {
  2. [E in Events as E['kind']]: (event: E) => void;
  3. }
  4. type SqureEvent = { kind: 'squre', x: number, y: number };
  5. type CircleEvent = { kind: 'circle', radius: number };
  6. type Config = EventConfig<SqureEvent | CricleEvent>
  7. /**
  8. type Config = {
  9. squre: (event: SqureEvent) => void;
  10. circle: (event: CircleEvent) => void;
  11. }
  12. /

映射类型的进一步应用

映射类型也可以和本章(类型操控)介绍的其它特性搭配使用。举个例子,下面是一个使用了条件类型的映射类型,根据对象是否有一个设置为字面量 true 的属性 pii,它会返回 true 或者 false

  1. type ExtractPII<Type> = {
  2. [Property in keyof Type]: Type[Property] extends { pii: true } ? true : false
  3. };
  4. type DBFileds = {
  5. id: { format: 'incrementing' };
  6. name: { type: string; pii: true };
  7. };
  8. type ObjectsNeedingGDPRDeletion = ExtractPII<DBFileds>;
  9. ^
  10. /*
  11. type ObjectsNeedingGDPRDeletion = {
  12. id: false;
  13. name: true;
  14. }
  15. */