开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情
前言
在 TypeScript 中,当需要从另一种类型派生(并保持同步)另一种类型时,使用映射类型会特别有用。
// 用户的配置值
type AppConfig = {
username: string;
layout: string;
};
// 用户是否有权更改配置值
type AppPermissions = {
changeUsername: boolean;
changeLayout: boolean;
};
在上面的代码中,AppConfig 和 AppPermissions 之间是存在隐式关系的,每当向 AppConfig 添加新的配置值时,AppPermissions 中也必须有相应的布尔值。
这里可以使用映射类型来管理两者之间的关系:
type AppConfig = {
username: string;
layout: string;
};
type AppPermissions = {
[Property in keyof AppConfig as `change${Capitalize<Property>}`]: boolean
};
在上面的代码中,只要 AppConfig 中的类型发生变化,AppPermissions 就会随之变化。实现了两者之间的映射关系。
概念
在 TypeScript 和 JavaScript 中,最常见的映射就是 Array.prototype.map():
[1, 2, 3].map(value => value.toString()); // ["1", "2", "3"]
这里,我们将数组中的数字映射到其字符串的表示形式。因此,TypeScript 中的映射类型意味着将一种类型转换为另一种类型,方法就是对其每个属性进行转换。
实例
下面来通过一个例子来深入理解一下映射类型。对设备定义以下类型,其包含制造商和价格属性:
type Device = {
manufacturer: string;
price: number;
};
为了让用户更容易理解设备信息,因此为对象添加一个新类型,该对象可以使用适当的格式来格式化设备的每个属性:
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 = {
manufacturer: string;
price: number;
releaseYear: number;
}
由于映射类型的强大功能,DeviceFormatter 类型会自动扩展为如下类型,无需进行任何额外的工作:
type DeviceFormatter = {
formatManufacturer: (value: string) => string;
formatPrice: (value: number) => string;
formatReleaseYear: (value: number) => string;
};