这两天东东遇到一个TS问题来找我。问题是这样的:对于这样的接口,我想获取userInfo的类型:interfaceResult{data?:{userInfo?:{name:string;}}}他是这样理解的:typeuserInfo=Result['data']['userInfo'];但是会报错:说userInfo不在这个联合类型上。这是正常的,因为可选索引的含义是联合类型值|未定义的值和未定义。所以他问我应该怎么得到它?我告诉他这个问题有两种不同复杂程度的解决方案,简单的和复杂的,并问他想听哪一种。他说想听简单的,我就让他这么写:typeuserInfo=Required['data']>['userInfo']Required是ts内置的高级类型是一个可选的索引类型修改删除。所以使用Required处理每一层,然后取索引的值。但是这样虽然简单,但是在需要取的层数太多的时候,要多次写Required还是挺麻烦的。然后冬冬问我是不是复杂的,应该怎么写?我跟他说,复杂的写起来比较麻烦,但是好处是使用简单,不管有多少层,你只需要处理一次:首先,你要知道如何Required是实现的:这里他使用了映射类型的语法,它是用来对索引类型做一些改动,生成一个新的索引类型。PinkeyofT是遍历索引类型T中的所有索引P,构造一个新的索引类型,值不变,即T[P]。在构建过程中,可以添加可选装饰,也可以去除可选装饰,对value和index进行一些修改。所以Partial,相对于Required,是这样实现的:我们要一次处理所有level,去掉optional修改,所以需要递归处理,即:typeDeepRequired={[KeyinkeyofObj]-?:IsOptionalextendsnever?Obj[Key]:DeepRequired}遍历索引类型Obj中的所有索引键,通过-?去掉可选的,然后对值进行判断,如果还是可选的索引,则为递归处理。那么如何实现这个IsOptional来判断索引是否为可选的高级类型呢?判断某种类型取决于它的性质。可选属性是值|未定义,这意味着它可能是空的。可选判断可以这样实现:typeIsOptional={}extendsPick?关键:从不;Obj是一个索引类型,Key是他的一个索引,因为optional索引的性质是可能为空,所以{}可能是索引类型的子类型。这里的Pick也是一个内置的高级类型,它的作用是从索引中取出一部分来构造一个新的索引类型:也是通过映射类型的语法来实现的:这里的a可能不存在,所以当它不存在时,不就是{}吗?所以可以使用{}extendsPick来判断是否为可选索引。综上所述,递归去除索引类型的可选修改是这样实现的:typeIsOptional={}extendsPick?Key:never;typeDeepRequired={[KeyinkeyofObj]-?:IsOptionalextendsnever?Obj[Key]:DeepRequired}测试一下:现在只需要处理一次,就可以取任意层级的索引值,方便了很多。其实这样写就可以用了,但是有时候会遇到这样的问题:TS不计算最后的结果。这就是TS的机制。默认是惰性计算,不需要的就不计算。那怎么让他算出最后的结果呢?只需要加一段触发计算的逻辑,比如xxxextendsany,肯定会成立:再测试一下,会发现TS已经计算出了最终结果:Summary要获取可选索引的值,需要要先使用Required去掉可选的索引类型再取。但是当层数过多时,逐层处理就很麻烦,可以用类型编程递归处理。使用映射类型的语法去掉索引类型的可选修饰,判断值的类型,如果还是可选索引,继续递归处理。判断可选指标是根据可选的性质来的。可选索引的值为value|undefined,所以如果{}extendsPick成立,说明Key是可选的。你可能会遇到没有计算所有类型的问题。这就是TS的机制。默认是惰性计算。您可以添加不影响结果的条件类型,例如xxextendsany来触发计算。Required在层数少的时候逐层处理比较简单,但是层数多的时候递归处理会更方便,而且这样的高级类型可以复用,可以在其他地方使用。这也是一种类型编程的好处。