深入了解 TypeScript 中的映射类型

128 阅读1分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情

前言

在 TypeScript 中,当需要从另一种类型派生(并保持同步)另一种类型时,使用映射类型会特别有用。

// 用户的配置值
type AppConfig = {
  usernamestring;
  layoutstring;
};

// 用户是否有权更改配置值
type AppPermissions = {
  changeUsernameboolean;
  changeLayoutboolean;
};

在上面的代码中,AppConfigAppPermissions 之间是存在隐式关系的,每当向 AppConfig 添加新的配置值时,AppPermissions 中也必须有相应的布尔值。

这里可以使用映射类型来管理两者之间的关系:

type AppConfig = {
  usernamestring;
  layoutstring;
};

type AppPermissions = {
  [Property in keyof AppConfig as `change${Capitalize<Property>}`]: boolean
};

在上面的代码中,只要 AppConfig 中的类型发生变化,AppPermissions 就会随之变化。实现了两者之间的映射关系。

概念

TypeScriptJavaScript 中,最常见的映射就是 Array.prototype.map():

[123].map(value => value.toString()); // ["1", "2", "3"]

这里,我们将数组中的数字映射到其字符串的表示形式。因此,TypeScript 中的映射类型意味着将一种类型转换为另一种类型,方法就是对其每个属性进行转换。

实例

下面来通过一个例子来深入理解一下映射类型。对设备定义以下类型,其包含制造商和价格属性:

type Device = {
  manufacturerstring;
  pricenumber;
};

为了让用户更容易理解设备信息,因此为对象添加一个新类型,该对象可以使用适当的格式来格式化设备的每个属性:

type DeviceFormatter = {
  [Key in keyof Device as `format${Capitalize<Key>}`]: (value: Device[Key]) => string;
};

我们来拆解一下上面的代码。Key in keyof Device 使用 keyof 类型运算符生成 Device 中所有键的并集。将它放在索引签名中实际上是遍历 Device 的所有属性并将它们映射到 DeviceFormatter 的属性。

format${Capitalize<Key>} 是映射的转换部分,它使用 key 重映射和模板文字类型将属性名称从 x 更改为 formatX。

(value: Device[Key]) => string; 利用索引访问类型 Device[Key] 来指示格式化函数的 value 参数是格式化的属性的类型。因此,formatManufacturer 接受一个 string(制造商),而 formatPrice 接受一个number(价格)。

下面是 DeviceFormatter 类型的样子:

type DeviceFormatter = {
  formatManufacturer(value: string) => string;
  formatPrice(value: number) => string;
};

现在,假设将第三个属性 releaseYear 添加到 Device 类型中:

type Device = {
  manufacturerstring;
  pricenumber;
  releaseYearnumber;
}

由于映射类型的强大功能,DeviceFormatter 类型会自动扩展为如下类型,无需进行任何额外的工作:

type DeviceFormatter = {
  formatManufacturer(value: string) => string;
  formatPrice(value: number) => string;
  formatReleaseYear(value: number) => string;
};