TypeScript8-映射类型

一、映射类型

TypeScript 提供了从旧类型中创建新类型的一种方式——映射类型。在映射类型中,新类型以相同的形式去转换旧类型里每个属性。例如,我们可以让接口中的每个属性成为只读属性或可选属性。

interface Obj {
    x: number;
    y: string;
    n: any
}

我们可以使用 type 来声明一个泛型接口,结合索引类型,来声明一个映射类型。

type Mapping<T> = {
    [P in keyof T]: number;
}

这个映射类型的作用是产生一个新类型,新类型中的成员和接口 Obj 相同,但成员的值的类型都是 number。接着我们就可以使用映射类型来改变已有的类型。

type MappingObj = Mapping<Obj>;

let obj: MappingObj = {
    x: 1,
    y: 2,
    n: 3
};

此时 MappingObj 就是一个由映射类型 Mapping 产生的新类型,它拥有和接口 Obj 相同的成员 x、y 和 n,但成员的值的类型都是 number。

二、一些常用的 TS 内置类型

接下来介绍一些 TS 的内置类型,可以用来作为映射类型。

1. ReadOnly 接口

如果我们想让接口 Obj 中的变为只读怎么办?有一个特别简单的方法,就是直接使用 TS 的内置类型 Readonly。

type ReadonlyObj = Readonly<Obj>;

首先我们定义了一个类型别名 ReadonlyObj,值为 TS 内置的类型 Readonly,传入的值是 Obj。ReadonlyObj 的类型和 Obj 是一样的,只是所有成员都变成了只读。

那这种内置的接口是如何实现的呢?我们来看下相关的源码:

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
}

我们来看下 Readonly 的实现,首先这是一个泛型接口,而且是一个有索引类型的泛型接口。它的索引签名是 "P in keyof T",其中 keyof T 就是一个索引类型的查询操作符,它表示 T 的所有属性的联合类型("x | y | n" 这种形式的属性列表)。这里的 P in 相当于 for in 操作,类型变量 P 会依次绑定到每个属性。

索引签名的返回值就是一个索引访问操作符了。这里的 T[P] 中 T 表示传入的对象,P 表示依次绑定的属性,T[P] 则为 P 属性的类型。最后前面加上 readonly 映射原始类型的所有属性,就把所有的属性变成了只读。

type Readonly<对象> = {
    readonly 属性列表[0]: 结果类型;
    readonly 属性列表[1]: 结果类型;
    readonly 属性列表[2]: 结果类型;
}

以上就是内置接口 Readonly 的实现了。

2. Partial 类型

如果我们想把一个接口的属性都变成可选的怎么办?

type PartialObj = Partial<Obj>;

使用内置的 Partial 类型,这样新的类型就能把成员变成可选。

源码如下:

type Partial<T> = {
    [P in keyof T]?: T[P] | undefined;
}

这个类型和刚刚的 Readonly 类型的实现几乎是一样的,只不过加上了 "?" 把属性变成了可选。

3. Pick 类型

Pick 类型可以抽取 obj 的一些子集,它接收两个参数,第一个参数就是接口 Obj,第二个参数就是我们要抽取的属性 key。

type PickObj = Pick<Obj, 'x' | 'y'>;

这样接口的 x 和 y 成员就能被单独抽取出来,形成一个新的类型。

源码实现:

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

第一个参数 T 表示我们要抽取的对象,第二个参数是 K,有个约束就是,K 一定要是来自变量 T 属性字面量的联合类型。

4. Record 类型

Record 类型创建了一个拥有 keys 类型的属性和对应值的 Type 的对象。

interface Obj {
    x: number,
    y: string,
    n: any
}

type RecordObj = Record<'a' | 'b', Obj>;

let obj: RecordObj = {
    a: { x: 1, y: '1', n: 2},
    b: { x: 2, y: '3', n: 4}
};

这里我们需要预定义一些新的属性 a 和 b,第二个参数是来自一个我们已知的类型。这样新的类型就有一些属性由 Record 第一个参数指定,类型由 Record 第二个参数指定。这种类型就是一种非同态的类型。

可以看到 Record 类型的好处是简明的。当我们想要去限制属性时,也就是 Record 类型大显身手的时候。下面的示例是我们在 Record 中使用字面量的联合类型去限制属性键。

type Roles = 'tester' | 'developer' | 'manager';

const staffCount: Record<Roles, number> = {
    tester: 10,
    developer: 20,
    manager: 1
};

在示例中,我们使用联合类型约束定义了一个类型。如果我们尝试去访问一个不在联合类型中的属性时,集成开发环境就会进行提示。当我们维护一个复杂类型的时候这非常有用,因为编译器会阻止这类错误的发生。Record 接口另一个有用的功能是 keys 可以是枚举。在下面的例子中,我们使用 StaffTypes 枚举作为 Record 类型的限制词,因此可读性更好。

enum StaffTypes {
    tester = 'tester',
    developer = 'developer',
    manager = 'manager' 
}

const staffCount: Record<StaffTypes, number> = {
    tester: 10,
    developer: 20,
    manager: 1
};

请注意,在 TypeScript 2.9后才支持枚举。因此,在2.9版本之前,key 的类型被限制为 string 类型。 

Record 类型还可以和 key of 组合。通过使用 key of 从现有类型中获取所有的属性,并和 string 组合,我们可以做如下事情:

interface Staff {
    name: string,
    salary: number
}

type staffJson = Record<keyof Staff, string>;

const product: staffJson = {
    name: 'John',
    salary: '3000'
}

当我们想要保留现有类型的属性但将值类型转换为其他类型时,这很便捷。

Record 类型的源码实现如下:

type Record<K extends string | number | symbol, T> = { 
    [P in K]: T;
}

K entends string | number | symbol 约束 K 必须为 string、number、symbol 类型或联合类型,每个属性 P in K,都转换为 T 类型。

总结:映射类型本质上是一种预设类型的泛型接口,通常还会集合到索引类型获取对象的属性和属性值,从而把一个对象变成我们想要的结构。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晴雪月乔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值