最近有两个同学问我项目中遇到的ts问题。这两个问题通常可以通过类型编程来解决。这两个是项目中遇到的真实问题,一起来看看:第一个问题是这个,接口返回的数据类型是项目中定义的,例如:填充数据时,必须根据type定义:但是,如果你想扩展一些属性,就会报错:但是现在你想在每一层灵活地扩展一些属性,怎么办?简单来说就是:这种索引类型如何灵活地添加一些额外的索引?这样,添加一个可索引的签名:可以添加额外的满足这个索引签名的索引。也可以这样写:取与Record的交集类型。ThisRecord是内置的高级类型,用于根据传入的key和value的类型生成索引类型:生成索引类型的语法称为映射类型。所以,Record是这样的,也是一个带有可索引签名的索引类型:我们知道如何处理普通对象,但是多层对象呢?这样一个任意层数的索引类型,如何在每一层添加Record?这时候就会用到递归,可以这样写:typeDeepRecord>={[KeyinkeyofObj]:Obj[Key]extendsRecord?DeepRecord&Record:Obj[Key]}&Record;定义一个DeepRecord的高级类型,传入的类型参数Obj为索引类型,受Record约束。然后通过映射类型的语法构造一个新的索引类型。Key来自于之前索引类型的Key,即KeyofObj中的Key。Value需要判断是否是索引类型,如果还是Record,则递归处理其值Obj[Key],否则直接返回Obj[Key]。每层都必须采用Record的交集类型。这样就完成了递归的让Obj的每一层都可扩展的目的。我们来测试一下:我们可以看到处理后的类型确实为每一层添加了Record。也确实每一层都可以扩展:并且有类型定义的索引也会做类型检查:总结一下:可索引签名可以允许索引类型扩展任意数量的满足签名的索引。如果你想给每一层任何级别的索引添加一个可索引的签名将需要递归处理。没有类型的编程呢?那你得原封不动的写一个新的索引类型,然后手动给每一层加上一个可索引的签名,太麻烦了,而且不通用。这就是类型编程的意义之一,类型可以根据需要修改。再来看第二个问题:即当一个索引为'desc'|'asc',其他索引为假。这个类型怎么写?有同学说,这是把所有的情况都枚举出来了,比如:这样真的可以解决问题:我们可以看到typecheck满足了我们的需求。但是如果我再添加几个属性呢?还有更多可能的类型吗?人工维护太麻烦!这时候可以通过类型编程动态生成。比如我定义了这样一个高级类型:typeGenerateType={[KeyinKeys]:{[Key2inKey]:'desc'|'asc'}}它生成的类型是这样的:这个还是很容易理解的,映射类型是用来生成索引类型的。我们可以取它的值:结果是这样的:现在只剩下那些为假的索引。Keys是联合类型,从中去掉Key的类型,可以使用Exclude,即Exclude。那么这个类型可以这样写:typeGenerateType={[KeyinKeys]:{[Key2inKey]:'desc'|'asc'}&{[Key3inExclude]:false}}[Keys]结果就是我们想要的类型:任意多个索引可以动态生成复合要求的联合类型。上面的高级类型也可以通过将key约束换成keyofany来优化:keyofany的结果是index的类型:但是有一个配置项叫keyofStringsOnly,只有开启keyofStringsOnly后才能作为key使用:keyofany动态获取密钥的可能类型比硬编码更好。这种高级类型最终会变成这样:'asc'}&{[Key3inExclude]:false}}[Keys]总结:当需要枚举多种类型的可能性时,可以通过类型编程动态生成。没有类型的编程呢?然后你必须手动维护所有可能的类型。这是类型编程的第二层含义,可以动态生成类型。通过这两个真实案例,你体会到类型编程解决了哪些问题?当需要修改现有类型,或动态生成类型时,可以使用类型编程。在第一种情况下,我们递归地向每一层添加可索引签名,而无需手动逐层修改。在第二种情况下,我们动态生成所有可能的类型,无需手动枚举。感受到类型编程的意义了吗?