TS - 映射类型(Mapped Types)、in

简单的例子

以下是一个简单的例子,通过索引访问类型(Indexed Access Types),可以给一个对象定义 key 的类型以及 value 的类型。

type Horse = {
  age: number;
};

type OnlyBoolsAndHorses = {
  [key: string]: boolean | Horse;
};

迭代联合类型

可以提供一个联合类型给索引访问类型,通过 in 关键字迭代这个联合类型并创建 key 的类型和 value 的类型:

file:[in 迭代 T 泛型的 key]
type OptionsFlags<T> = {
  [P in keyof T]: boolean;
};

[P in keyof T] 通过关键字 keyof 获取对象类型的所有 key,这些 key 组合成一个联合类型,然后通过 in 迭代联合类型,并创建 OptionsFlags 的所有 key,并且这些 key 的值类型都是 boolean。

file:[使用 OptionsFlags]
type Features = {
  darkMode: () => void;
  newUserProfile: () => void;
};

type FeatureOptions = OptionsFlags<Features>;
// ^?
//type FeatureOptions = {
//    darkMode: boolean;
//    newUserProfile: boolean;
//}

从以上实例中得知,最终我们得到了一个新的类型 FeatureOptions,它具有与 Features 类型相同的属性,但其值都被更改为 boolean 类型。

映射修改器(Mapping Modifiers)

修改 readonly

我们可以移除一个类型的 readonly 关键字,让它变得可读可写的状态。如下所示,创建一个 CreateMutable 类型别名,删除 readonly 关键字,就在其前面添加一个 -,反之,添加 readonly 关键字,就在其前面添加一个 +

// 移除 readonly
type CreateMutable<Type> = {
  -readonly [Property in keyof Type]: Type[Property];
};

type LockedAccount = {
  readonly id: string;
  readonly name: string;
};
// ^?
//type LockedAccount = {
//    readonly id: string;
//    readonly name: string;
//}

type UnlockedAccount = CreateMutable<LockedAccount>;
//   ^?
//type UnlockedAccount = {
//    id: string;
//    name: string;
//}

LockedAccount 类型别名中的所有字段都是 readonly,把这个类型别名传递给 CreateMutable 类型别名之后,通过映射修改器删除所有字段的 readonly,得到 UnlockedAccount,最终如上所示,已经没有了 readonly 关键字。

修改可选属性

它也可以修改对象类型的可选属性,删除可选就在 ? 前添加一个 -,反之,添加可选就在属性前添加一个 +?。具体如下所示:

// 移除可选属性
type Concrete<Type> = {
  [Property in keyof Type]-?: Type[Property];
};

type MaybeUser = {
  id: string;
  name?: string;
  age?: number;
};

type User = Concrete<MaybeUser>;
// ^?
//type User = {
//    id: string;
//    name: string;
//    age: number;
//}

键重新映射(Key Remapping)

在 TS 4.1 版本之后,我们可以通过 as 关键字对键重新映射,改变它的类型。

新的属性名

通过模板文字类型修改键的名称,具体如下所示:

file:[生成 getters 属性]
type Getters<T> = {
    [P in keyof T as `get${Capitalize<string & P>}`]: () => T[P]
};

获取 T 对象类型的所有键名的联合类型。P 代表一个个键名,由 in 迭代联合类型而来。as 关键字对前面进行重命名操作,重命名的结果是后面模板文字类型。

Capitalize 类型别名是 TS 内置的工具类型,它把一个单词的首字母转换为大写字母。由于这个类型别名工具的类型参数需要一个 string 类型的,而 P 是一个 string | number | symbol 类型,因此,string & P 的结果是 string。

T[P] 就是取联合类型中的类型元素,其实我们可以把联合类型看作是一个数组(或者集合),取值就通过名称来取。

tip:[start]交叉运算符 & 在合并类型过程中,如果一个类型包含了其他类型的子集,那么结果类型将取该子集类型。

类型 string | number | symbol 是一个联合类型,表示一个值可以是 string、number 或 symbol 中的任意一种类型。

当我们使用交叉运算符 & 将这两个类型进行交叉操作时,由于 string 类型是 string | number | symbol 的子集,交叉类型的结果将取 string 类型。

type R = (string | number | symbol) & string;
// ^? string

tip:[end]

以下是使用 Getters 类型别名的结果,它会把我们的 Person 接口下的所有属性名称转换为 getXxx 的形式,并且值的类型是一个函数类型,函数的返回值类型是由属性的类型决定的:

interface Person {
    name: string;
    age: number;
    location: string;
}

type LazyPerson = Getters<Person>;
//    ^?
//type LazyPerson = {
//    getName: () => string;
//    getAge: () => number;
//    getLocation: () => string;
//}

过滤属性

P 如果是 kind,就返回 never,移除本次迭代,保留非 kind 字段名。

type RemoveKindField<T> = {
    [P in keyof T as Exclude<P, "kind">]: T[P]
};

interface Circle {
    kind: "circle";
    radius: number;
}

type KindlessCircle = RemoveKindField<Circle>;
//   ^? type KindlessCircle = { radius: number; }

复杂联合类型

除了对基础联合类型,比如 string | number | symbol 这样以外的操作以外,还可以操作复杂一点的类型。

type EventConfig<Events extends { kind: string }> = {
    [E in Events as E["kind"]]: (event: E) => void;
}

type SquareEvent = { kind: "square", x: number, y: number };
type CircleEvent = { kind: "circle", radius: number };

type Config = EventConfig<SquareEvent | CircleEvent>
// ^?
//type Config = {
//    square: (event: SquareEvent) => void;
//    circle: (event: CircleEvent) => void;
//}

Events 扩展一个对象类型,意思是说,对联合类型中,扩展每一个元素的属性,如果不这样做,在 E["kind"] 的时候会报错,因为不知道这个 E 会有 kind 属性。然后,在索引访问类型中迭代 Events 时进行键重新映射(Key Remapping)操作。

tip:[start]在这里 Events 是一个联合类型,这里和最开始的 迭代联合类型 小节是一样的,迭代的是联合类型,而不是迭代某个对象类型中的键组成的联合类型,那个是 E in keyof Event 之类的。tip:[end]

假如,kind 名字是 "square",那么就是 square: (event: SquareEvent) => void,如上所示。

属性值的条件类型

属性值还可以使用条件类型来决定本次迭代的值类型是什么,如下所示:

type ExtractPII<Type> = {
  [Property in keyof Type]: Type[Property] extends { pii: true } ? true : false;
};
posted @ 2023-07-06 02:19  Himmelbleu  阅读(3)  评论(0编辑  收藏  举报