TypeScript 的映射类型 Mapped types (e.g. { [P in K]: T[P] })

5,734 阅读3分钟

在JS中我们可以通过 for...in 遍历出一个 object{} 的所有 key 然后进行一些逻辑处理,那么在 TS 中是否有类似的功能用于遍历 interface{} ,在 TS2.1 版本就推出了此能力。

它得出的结果只能赋值给类型,最简单的书写格式:

{ [ K in P ] : T }

下面我们对其中的 KPT 各自表示什么,都进行详细的说明。

利用映射类型快速创建类型

type Coord = {
    [K in 'x' | 'y']: number;
};
// 得到
// type Coord = {
//  x: number;
//  y: number;
// }

首选确定它执行了一个循环(可以理解为类似 for...in 的效果),这里的 P 直接设置为 'x' | 'y' 的一个联合类型,而 K 是一个标识符,它映射为 P 的每一个子类型。T 为数据类型,我们直接固定为 number,也可以是任何其他复杂类型。

因为 T 值数据类型可以是任何值,甚至数值 1 也可以,因此我们把数据类设成 K 自身也行:

type Coord = { [K in 'x' | 'y']: K };
// 得到 type Coord = { x: 'x'; y: 'y'; }

利用映射类型进行复制

type Item = {
    a: string
    b: number
    c: boolean
}

// Item 的所有属性的一个 联合类型
type ItemKeys = 'a' | 'b' | 'c';
// 也可以简写为:
// type ItemKeys = keyof Item;

type Copy = { [K in ItemKeys]: Item[K] };
// 得到 type Copy = { a: string, b: number, c: boolean };

这里的 Copy 类型与 Item 中的声明完全一样,可以简写为:

type Item = { a: string, b: number, c: boolean };
type Copy = { [K in keyof Item]: Item[K] };
// 得到 type Copy = { a: string, b: number, c: boolean };

基于此特性再结合 索引访问类型,我们可以封装出一个复制效果的函数类型:

type Copy<P> = { [K in keyof P]: P[K] }

type Item = { a: string, b: number, c: boolean };
type ItemCopy = Copy<Item>;
// 得到 type ItemCopy = { a: string, b: number, c: boolean };

生成类型的函数类型

基于此特性封装一个快速生成接口类型的函数类型:

type Create<P extends keyof any, T> = { [K in P]: T };

type Coord = Create<'x' | 'y', number>;
// 得到 type Coord = { x: number, y: number };

如果你很眼熟,则说明你见过或用过官方预置的高级类型 Record<K extends keyof any, T> ,是的,他们一模一样。

这里的 extends 条件类型用于继承的作用,此时我们传入的 联合类型。因 P 必须可分配给 string 类型的原因,我们需要进行一些限制,确保接收到的 P 是有效的值。

而 keyof 的一个特性: keyof T的类型会被认为是 stringnumbersymbol 的子类型。(关于 keyof 的更多特性之后将讲解)基于 keyof any 的检测,因此下面的使用都会报错:

type Val = Create<true, boolean>;
// Error: Type 'true' does not satisfy the constraint 'string | number | symbol'.

type Val2 = Create<() => void>;
// Error: Type '() => void' does not satisfy the constraint 'string | number | symbol'

映射类型的装饰符

还可以使用 readonly? 对属性进行设置:

type Coord = {
    readonly [K in 'x' | 'y']: number
};
// 得到
// type Coord = {
//  readonly x: number;
//  readonly y: number;
// };

两个装饰符也可组合使用:

type Coord = {
    readonly [K in 'x' | 'y']?: number;
};
// 得到
// type Coord = {
//  readonly x?: number;
//  readonly y?: number;
// };

但这里面也有局限性,无法去除属性上已经存在的 装饰符:

type CoordOptional = {
    x?: number;
    readonly y: number;
};
type Coord = {
    [K in keyof CoordOptional]: number;
};
// 得到
// type Coord = {
//  x?: number;
//  readonly y: number;
// };

因为这个原因,社区又在 TS2.8 又对其进行了完善,可以在上面的装饰符添加 -+ 符号:

type CoordOptional = {
    x?: number;
    readonly y: number;
};
type Coord = {
    -readonly [K in keyof CoordOptional] -?: number;
};
// 得到
// type Coord = {
//  x: number;
//  y: number;
// };