当前位置: 首页 > 科技观察

类型操:探索TypeScript内置的高级类型

时间:2023-03-19 18:15:48 科技观察

大家好,我是前端西瓜哥,今天来做TS类型操。TypeScript类型编程TypeScript的类型系统,最基本的是简单对应JavaScript的基本类型,如string、number、boolean等,然后是新的增强类型,如元组、枚举、复合类型、交集类型、和索引类型。这里会有一个问题,就是函数声明支持不同类型的重复写问题。例如,我的一个函数需要接收一个数组,然后从中取出一个元素。一旦我们传入的数组类型不同,就得额外写一个类型别名,太麻烦了。typegetStrItem=(items:string[])=>string;typegetNumItem=(items:number[])=>number;//...每个额外的类型都需要写一个额外的类型别名constgetStrFirst:getStrItem=(a)=>{returna[0];}为了解决这个问题,TypeScript引入了泛型,让类型也可以是参数。typegetItem=(items:T[])=>TconstgetStrFirst:getItem=(a)=>{returna[0];}上面的T是一个类型参数,当我们传递类型Alias<具体类型>形式(上面代码对应getItem),我们可以得到具体的类型。鉴于JavaScript过于灵活,TypeScript实现了结构化的类型系统,我们觉得泛型简单的推到T的粒度不够细,希望能够获取到T的内部结构。因此,TypeScript提供了类型基于泛型的编程。通过一些语法,我们可以得到T下更细粒度的类型,或者通过判断得到其他类型。这也称为体操。也许是因为太花哨了,就像参加体操比赛一样。总结一下,从增强类型能力的过程来看,就是:基础类型->泛型->类型编程(类型体操)TypeScript内置的高级类型TS代码版本是4.8.2我们来看看内置的使用类型编程的TypeScript高级类型。PickPick的作用是从T类型(对象类型)中提取以K(联合类型)分隔的key,返回一个新的对象类型。image-20220919230929011这里我们通过Pick提取出需要的pos和radius物理信息属性。查看Pick的实现:/***FromT,pickasetofpropertieswhosekeysintheunionK*/typePick={[PinK]:T[P];};首先我们来看等号左边的。类型参数有两个,T和K。先说类型参数的命名。类型变量的命名和写JS变量一样,可以随意命名。但是建议首字母大写,防止和一些全是小写的关键字(比如extends、as、infer)混淆。T通常代表一个要分析的类型(Type),K通常代表对象属性名(Key)。就像数学中函数的x和y,想不出好名字就用这两个吧。keyof是一种类型运算符,用于提取对象的属性(键)并将它们组装成一个联合类型。extends用于限制类型参数的范围。例如,表示T类型必须是string的子类,文字“a”或string都是string的子类。如果不是字符串子类,编译会失败。还有一种类似于JS中的三元运算符的语法,它扩展了?:,在等号右边,用于实现条件判断。和前面说的extends不是一回事,后面会讲到。好了,我们来看看是什么意思。意思是传入了T和K两个类型参数,那么K一定是T的属性组成的联合类型的一部分。让我们看看{[PinK]:T[P];};在等号右边,重新映射类型。in用于遍历联合类型。即遍历我们需要的key作为索引P,然后用对应的T[P]作为它的value。ExcludeExclude的作用是从联合类型中排除某些类型。实现如下:/***从T中排除那些可赋值给U的类型*/typeExclude=TextendsU?从不:T;这里涉及到一个经常使用的条件语法:extends?:,可以类比成JS中的三元表达式(即condition?a:b)。为了更好的解释,我们实现了一个类型IsNumber来判断一个类型是否是数值类型。typeIsNumber=Textendsnumber?true:false;//使用typeA=IsNumber<1>//truetypeB=IsNumber<"str">//falseTextendsnumber判断T是否是number的子类,是则返回true,否则为假。需要注意的是,它和之前的类型参数extends是完全不同的东西。回到我们的Exclude,逻辑很清晰,就是判断T是否是U的子类,如果是则返回never(效果是要丢弃);否则返回T“乙”|"c"不是"b"的子类,返回"a"|“乙”|“C”?你是如何编程“a”的?“C”?其实这就是联合类型的特殊逻辑。如果联合类型使用extends,会打散变成多个独立的类型进行判断,最后合并。所以真正的逻辑是"a"|“乙”|将“c”打散变成依次判断“a”、“b”、“c”是否是“b”的子类,分别得到“a”、“c”never、“c”,然后结合起来,变成“一个”|“C”。ReturnType获取函数类型的返回值类型。实现为:/***获取函数类型的返回类型*/typeReturnTypeany>=Textends(...args:any)=>inferR?R:any;(...args:any)=>等号左边的any代表任意函数类型,用于限制传入参数的类型。然后我们看到了一个新的关键字infer,它代表引用,用于类型推导。extends和infer的结合可以实现模式匹配。如果extends匹配成功,infer可以推导出对应的类型。如果你了解JS正则表达式,你会发现它们非常相似,infer就像一个捕获组。'ABC'.replace(/A(.)C/,'$1')//'B'。提取与模式匹配的字符串InTextends(...args:any)=>inferR?R:any;,我们为返回值部分设置了infer,提供了一个局部变量R。如果extends条件判断为继承关系,那么变量R就是赋值函数的返回值。下面判断为真的分支(?后面的表达式)可以得到这个R。判断为假的分支因为匹配失败,所以得不到。这个extends+infer其实就是类型体操的精髓所在。您可以继续拆分传入类型T以获得更细粒度的类型。更多类型的体操学习和更多类型的编程技巧由于篇幅原因不再赘述,例如:asoperatorcandotypeindexremapping;数组的“长度”可以用来实现数值运算;通过递归实现循环逻辑;一些特殊的type()处理等。TypeScript的类型是图灵完备的,可以实现各种判断、循环、加减法逻辑。当然,有些逻辑实现起来非常繁琐。它的语法也不同:它进行“压缩”。一类编程只是一个表达式,需要用extend的方式嵌套?:实现逻辑。学习TS体操从某种意义上说确实有点像学习一门新的语言,而且有点怪怪的。曾经梦想成为一名体操运动员,于是看了官方文档,发现文档很零散,而且还是英文的。我跌跌撞撞,很快就放弃了。我不禁在心里嘀咕:体操好难学,一塌糊涂。我当时想,有没有关于体操的好课?直到最近才发现,掘金推出的课程:《TypeScript 类型体操通关秘籍》算不上是新课程,但是写的真的很好。这是我认为体操最好的书面课程,24节,只需要29.9。性价比太高了。如果我提供这门课程,如果它的质量如此,我会以99.9%的价格出售它。