TS 类型编程:索引类型递归去掉可选修饰

开发 前端
想取一个可选索引的值,需要先用 Required 把索引类型去掉可选然后再取。但是当层数多了的话,这样一层层处理挺麻烦的,可以用类型编程递归处理下。

这两天东东遇到一个 TS 的问题,跑来问我。

问题是这样的:

这样一个 interface,想取出 userInfo 的类型来:

interface Result{
data?: {
userInfo?: {
name: string;
}
}
}

他是这样取的:

type userInfo = Result['data']['userInfo'];

但是会报错:

图片

说是 userInfo 不在这个联合类型上。

这很正常,因为可选索引的含义就是值和 undefined 的联合类型  value | undefined。

于是他问我应该怎么取?

我和他说这个问题有两种不同复杂度的解决方案,有简单的有复杂的,问他想听哪个。

他说想听简单的,于是我告诉他这样写:

type userInfo = Required<Required<Result>['data']>['userInfo']

Required 是 ts 内置的高级类型,是把索引类型的所有可选修饰去掉的。

图片

所以每一层用 Required 处理一下再取索引的值就可以了。

但是这样虽然简单,当取的层数多了要写很多次 Required,也挺麻烦的。

然后东东又问我如果是复杂的那个,要怎么写?

我和他说复杂的那个写起来麻烦一些,但好处是用起来简单,不管多少层都只需要处理一次:

首先要知道 Required 是怎么实现的:

图片

他这里用到了映射类型的语法,作用是对索引类型做一些修改,生成新的索引类型。

P in keyof T 就是遍历索引类型 T 中的所有索引 P,用来构造新的索引类型,值保持不变,也就是 T[P]。

构造的过程中可以加上可选的修饰、也可以去掉可选的修饰,还可以对值和索引做一些修改。

所以和 Required 相对的 Partial 就是这样实现的:

图片

我们想一次处理完所有层级,都把可选的修饰给去掉,那就要递归处理,也就是这样:

type DeepRequired<Obj> = {
[Key in keyof Obj]-?: IsOptional<Key, Obj> extends never? Obj[Key]: DeepRequired<Obj[Key]>
}

遍历索引类型 Obj 中的所有索引 Key,通过 -? 去掉可选,然后对值要做一下判断,如果还是可选索引,那就递归处理。

那怎么实现这个 IsOptional 的判断索引是否是可选的高级类型呢?

判断某个类型要根据他的性质来,可选的性质就是 value | undefined,也就是说可能是空。

可以这样来实现可选的判断:

type IsOptional<Key extends keyof Obj, Obj> = 
{} extends Pick<Obj, Key> ? Key : never;

Obj 是索引类型,Key 是他的某个索引,因为可选索引的性质是可能为空,所以 {} 就可能是索引类型的子类型。

这里的 Pick 也是内置的高级类型,作用是取出一部分索引构造新的索引类型:

图片

同样是通过映射类型的语法实现的:

图片

这里 a 可能是没有的,那当没有的时候不就是 {} 么?所以可以用 {} extends Pick<Obj, Key> 来判断是不是可选索引。

综上,递归去掉索引类型的可选修饰就是这样实现的:

type IsOptional<Key extends keyof Obj, Obj> = 
{} extends Pick<Obj, Key> ? Key : never;

type DeepRequired<Obj> = {
[Key in keyof Obj]-?: IsOptional<Key, Obj> extends never? Obj[Key]: DeepRequired<Obj[Key]>
}

我们测试一下:

图片

现在只要处理一次,就可以取任意层级的索引值了,方便了很多。

其实写成这样就可以用了,但是有时候你会遇到这样的问题:

图片

TS 没有把最终的结果计算出来。

这个是 TS 的机制,默认是懒计算的,用不到的不会计算。

那怎么让他计算出最终结果呢?

加上一段逻辑触发计算就可以了,比如 xxx extends any 这种肯定会成立的条件判断:

图片

再测试一下你就会发现 TS 计算出了最终的结果:

图片

总结

想取一个可选索引的值,需要先用 Required 把索引类型去掉可选然后再取。但是当层数多了的话,这样一层层处理挺麻烦的,可以用类型编程递归处理下。

用映射类型的语法去掉索引类型的可选修饰,判断值的类型,如果还是可选的索引,那就继续递归的处理。

判断可选索引是通过可选的性质来的,可选索引的值是 value | undefined, 所以 {} extends Pick<Obj, Key> 成立的话就代表这个 Key 是可选的。

可能会遇到类型没有全部计算的问题,这是 TS 的机制,默认是懒计算的,可以加上 xx extends any 这种不影响结果的条件类型来触发计算。

层层用 Required 处理在层数少的情况下比较简单,但层数多了的时候还是递归处理更方便一些,而且这样的高级类型是可以复用的,可以用在别的地方,这也是类型编程的好处。

责任编辑:姜华 来源: 神光的编程秘籍
相关推荐

2022-01-19 23:41:56

TS索引类型

2024-01-16 07:33:02

SwiftTypeScript可选绑定

2015-09-02 10:33:54

红包类型optionals

2022-02-12 22:16:53

TypeScript类型字符串

2010-10-08 13:53:14

2010-10-26 17:34:03

Oracle索引

2024-07-08 10:48:51

2022-12-30 08:08:30

2022-05-07 07:33:55

TypeScript条件类型

2022-04-29 06:54:48

TS 映射类型User 类型

2022-08-08 23:49:01

TypeScriptumd模块

2022-02-25 14:04:56

TS前端代码

2022-04-17 10:29:10

TSTypeScript对象类型

2011-11-03 16:32:57

Dart

2010-05-11 10:36:13

MySQL索引类型

2022-03-25 10:38:40

索引MySQL数据库

2010-10-12 13:14:11

mysql索引类型

2010-11-02 11:22:06

DB2索引类型

2015-08-03 17:03:29

swiftoptionals可选型

2023-10-13 08:00:00

网络层开发
点赞
收藏

51CTO技术栈公众号