TypeScript 的官方文档早已更新,但我能找到的中文文档都还停留在比较老的版本。所以对其中新增以及修订较多的一些章节进行了翻译整理。

本篇翻译整理自 TypeScript Handbook 中 「Mapped Types」 章节。

本文并不严格按照原文翻译,对部分内容也做了解释补充。

映射类型(Mapped Types)

有的时候,一个类型需要基于另外一个类型,但是你又不想拷贝一份,这个时候可以考虑使用映射类型。

映射类型建立在索引签名的语法上,我们先回顾下索引签名:

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

而映射类型,就是使用了 PropertyKeys 联合类型的泛型,其中 PropertyKeys 多是通过 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. // type FeatureOptions = {
  7. // darkMode: boolean;
  8. // newUserProfile: boolean;
  9. // }

映射修饰符(Mapping Modifiers)

在使用映射类型时,有两个额外的修饰符可能会用到,一个是 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. // type UnlockedAccount = {
  11. // id: string;
  12. // name: string;
  13. // }
  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. // type User = {
  12. // id: string;
  13. // name: string;
  14. // age: number;
  15. // }

通过 as 实现键名重新映射(Key Remapping via as

在 TypeScript 4.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. // type LazyPerson = {
  11. // getName: () => string;
  12. // getAge: () => number;
  13. // getLocation: () => string;
  14. // }

你也可以利用条件类型返回一个 never 从而过滤掉某些属性:

  1. // Remove the 'kind' property
  2. type RemoveKindField<Type> = {
  3. [Property in keyof Type as Exclude<Property, "kind">]: Type[Property]
  4. };
  5. interface Circle {
  6. kind: "circle";
  7. radius: number;
  8. }
  9. type KindlessCircle = RemoveKindField<Circle>;
  10. // type KindlessCircle = {
  11. // radius: number;
  12. // }

你还可以遍历任何联合类型,不仅仅是 string | number | symbol 这种联合类型,可以是任何类型的联合:

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

深入探索(Further Exploration)

映射类型也可以跟其他的功能搭配使用,举个例子,这是一个使用条件类型的映射类型,会根据对象是否有 pii 属性返回 true 或者 false :

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

TypeScript 系列

  1. TypeScript 之 基础入门
  2. TypeScript 之 常见类型(上)
  3. TypeScript 之 常见类型(下)
  4. TypeScript 之 类型收窄
  5. TypeScript 之 函数
  6. TypeScript 之 对象类型
  7. TypeScript 之 泛型
  8. TypeScript 之 Keyof 操作符
  9. TypeScript 之 Typeof 操作符
  10. TypeScript 之 索引访问类型
  11. TypeScript 之 条件类型

微信:「mqyqingfeng」,加我进冴羽唯一的读者群。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。