范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

来做操吧!深入TypeScript高级类型和类型体操

  TypeScript 给 JavaScript 扩展了类型的语法,我们可以给变量加上类型,在编译期间会做类型检查,配合编辑器还能做更准确的智能提示。此外,TypeScript 还支持了高级类型用于增加类型系统的灵活性。
  就像 JavaScript 的高阶函数是生成函数的函数,React 的高阶组件是生成组件的组件一样,Typescript 的高级类型就是生成类型的类型。
  TypeScript 高级类型是通过 type 定义的有类型参数(也叫泛型)的类型,它会对传入的类型参数做一系列的类型计算,产生新的类型。
  type Pick = {
  [P in K]: T[P];
  };
  比如,这个 Pick 就是一个高级类型,它有类型参数 T 和 K,类型参数经过一系列的类型计算逻辑,会返回新的类型。
  TypeScript 高级类型会根据类型参数求出新的类型,这个过程会涉及一系列的类型计算逻辑,这些类型计算逻辑就叫做类型体操。当然,这并不是一个正式的概念,只是社区的戏称,因为有的类型计算逻辑是比较复杂的。
  TypeScript 的类型系统是图灵完备的,也就是说它能描述任何可计算逻辑,简单点来说就是循环、条件判断等该有的语法都有。
  既然 TypeScript 的类型系统这么强,那我们就做一些高级类型的类型体操来感受下吧。
  我们会做这些体操:
  用 ts 类型实现加法
  用 ts 类型生成重复 N 次的字符串
  用 ts 类型实现简易的 js parser(部分)
  用 ts 类型实现对象属性按条件过滤
  我把这些体操分为数字类的、字符串类的、对象类的,把这三种类型计算逻辑的规律掌握了,相信你的体操水平会提升一截。
  TypeScript 类型语法基础
  在做体操之前,要先过一下 TypeScript 的类型语法,也就是能做哪些类型计算逻辑。
  既然说该有的语法都有,那我们来看下循环和判断都怎么做:
  ts 类型的条件判断
  图片
  ts 类型的条件判断的语法是 条件 ? 分支1 : 分支2 。
  extends 关键字是用于判断 A 是否是 B 类型的。例子中传入的类型参数 T 是 1,是 number 类型,所以最终返回的是 true。
  ts 类型的循环
  图片ts 类型没有循环,但可以用递归来实现循环。
  我们要构造一个长度为 n 的数组,那么就要传入长度的类型参数 Len、元素的类型参数 Ele、以及构造出的数组的类型参数 Arr(用于递归)。
  然后类型计算逻辑就是判断 Arr 的 length 是否是 Len,如果是的话,就返回构造出的 Arr,不是的话就往其中添加一个元素继续构造。
  这样,我们就递归的创建了一个长度为 Len 的数组。
  ts 类型的字符串操作
  ts 支持构造新的字符串:
  图片
  也支持根据模式匹配来取字符串中的某一部分:
  图片
  因为 str 符合 aaa, 的模式,所以能够匹配上,把右边的部分放入通过 infer 声明的局部类型变量里,之后取该局部变量的值返回。
  ts 类型的对象操作
  ts 支持对对象取属性、取值:
  图片
  也可以创建新的对象类型:
  图片
  通过 keyof 取出 obj 的所有属性名,通过 in 遍历属性名并取对应的属性值,通过这些来生成新的对象类型 newObj。
  我们过了一下常用的 ts 类型的语法,包括条件判断、循环(用递归实现)、字符串操作(构造字符串、取某部分子串)、对象操作(构造对象、取属性值)。接下来就用这些来做操吧。
  ts 类型体操练习
  我们把体操分为 3 类来练习,之后再分别总结规律。
  数字类的类型体操
  体操 1:实现高级类型 Add,能够做数字加法。
  ts 类型能做数字加法么?肯定可以的,因为它是图灵完备的,也就是各种可计算逻辑都可以做。
  那怎么做呢?
  数组类型可以取 length 属性,那不就是个数字么。可以通过构造一定长度的数组来实现加法。
  上文我们实现了通过递归的方式实现了构造一定长度的新数组的高级类型:
  type createArray = Arr["length"] extends Len ? Arr : createArray
  那只要分别构造两个不同长度的数组,然后合并到一起,再取 length 就行了。
  type Add = [...createArray, ...createArray]["length"]
  我们测试下:
  图片
  我们通过构造数组的方式实现了加法!
  小结下:ts 的高级类型想做数字的运算只能用构造不同长度的数组再取 length 的方式,因为没有类型的加减乘除运算符。
  字符串类的体操
  体操2:把字符串重复 n 次。
  字符串的构造我们前面学过了,就是通过 ${A}${B} 的方式,那只要做下计数,判断下重复次数就行了。
  计数涉及到了数字运算,要通过构造数组再取 length 的方式。
  所以,我们要递归的构造数组来计数,并且递归的构造字符串,然后判断数组长度达到目标就返回构造的字符串。
  所以有 Str(待重复的字符串)、Count(重复次数)、Arr(用于计数的数组)、ResStr(构造出的字符串)四个类型参数:
  type RepeactStr
  = Arr["length"] extends Count
  ? ResStr
  : RepeactStr;
  我们递归的构造了数组和字符串,判断构造的数组的 length 如果到了 Count,就返回构造的字符串 ResStr,否则继续递归构造。
  测试一下:
  图片
  小结:递归构造字符串的时候要通过递归构造数组来做计数,直到计数满足条件,就生成了目标的字符串。
  这个体操只用到了构造字符串,没用到字符串通过模式匹配取子串,我们再做一个体操。
  体操3: 实现简易的 JS Parser,能解析字符串 add(11,22) 的函数名和参数
  字符串的解析需要根据模式匹配取子串。这里要分别解析函数名(functionName)、括号(brackets)、数字(num)、逗号(comma),我们分别实现相应的高级类型。
  解析函数名
  函数名是由字母构成,我们只要一个个字符一个字符的取,判断是否为字母,是的话就记录下该字符,然后对剩下的字符串递归进行同样的处理,直到不为字母的字符,通过这样的方式就能取出函数名。
  我们先定义字母的类型:
  type alphaChars = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m"
  | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"
  | "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M"
  | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z";
  还有保存中间结果的类型:
  type TempParseResult = {
  token: Token,
  rest: Rest
  }
  然后就一个个取字符来判断,把取到的字符构造成字符串存入中间结果:
  type parseFunctionName
  = SourceStr extends `${infer PrefixChar}${infer RestStr}`
  ? PrefixChar extends alphaChars
  ? parseFunctionName
  : TempParseResult
  : never;
  我们取了单个字符,然后判断是否是字母,是的话就把取到的字符构造成新的字符串,然后继续递归取剩余的字符串。
  测试一下:
  图片
  图片
  符合我们的需求,我们通过模式匹配取子串的方式解析出了函数名。
  然后继续解析剩下的。
  解析括号
  括号的匹配也是同样的方式,而且括号只有一个字符,不需要递归的取,取一次就行。
  type brackets = "(" | ")";
  type parseBrackets
  = SourceStr extends `${infer PrefixChar}${infer RestStr}`
  ? PrefixChar extends brackets
  ? TempParseResult
  : never
  : never;
  测试一下:
  图片
  继续解析剩下的:
  解析数字
  数字的解析也是一个字符一个字符的取,判断是否匹配,匹配的话就递归取下一个字符,直到不匹配:
  type numChars = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
  type parseNum
  = SourceStr extends `${infer PrefixChar}${infer RestStr}`
  ? PrefixChar extends numChars
  ? parseNum
  : TempParseResult
  : never;
  测试一下:
  图片
  继续解析剩下的:
  解析逗号
  逗号和括号一样,只需要取一个字符判断即可,不需要递归。
  type parseComma
  = SourceStr extends `${infer PrefixChar}${infer RestStr}`
  ? PrefixChar extends ","
  ? TempParseResult<",", RestStr>
  : never
  : never;
  测试一下:
  图片
  至此,我们完成了所有的字符的解析,解析来按照顺序组织起来就行。
  整体解析
  单个 token 的解析都做完了,整体解析就是组织下顺序,每次解析完拿到剩余的字符串传入下一个解析逻辑,全部解析完,就可以拿到各种信息。
  type parse
  = parseFunctionName extends TempParseResult
  ? parseBrackets extends TempParseResult
  ? parseNum extends TempParseResult
  ? parseComma extends TempParseResult
  ? parseNum extends TempParseResult
  ? parseBrackets extends TempParseResult
  ? {
  functionName: FunctionName,
  params: [Num1, Num2],
  }: never: never: never: never : never : never;
  测试一下:
  图片
  大功告成,我们用 ts 类型实现了简易的 parser!
  小结:ts 类型可以通过模式匹配的方式取出子串,我们通过一个字符一个字符的取然后判断的方式,递归的拆分 token,然后按照顺序拆分出 token,就能实现字符串的解析。
  完整代码如下:
  type numChars = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
  type alphaChars = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m"
  | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"
  | "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M"
  | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z";
  type TempParseResult = {
  token: Token,
  rest: Rest
  }
  type parseFunctionName =
  SourceStr extends `${infer PrefixChar}${infer RestStr}`
  ? PrefixChar extends alphaChars
  ? parseFunctionName
  : TempParseResult
  : never;
  type brackets = "(" | ")";
  type parseBrackets
  = SourceStr extends `${infer PrefixChar}${infer RestStr}`
  ? PrefixChar extends brackets
  ? TempParseResult
  : never
  : never;
  type parseNum
  = SourceStr extends `${infer PrefixChar}${infer RestStr}`
  ? PrefixChar extends numChars
  ? parseNum
  : TempParseResult
  : never;
  type parseComma
  = SourceStr extends `${infer PrefixChar}${infer RestStr}`
  ? PrefixChar extends ","
  ? TempParseResult<",", RestStr>
  : never
  : never;
  type parse
  = parseFunctionName extends TempParseResult
  ? parseBrackets extends TempParseResult
  ? parseNum extends TempParseResult
  ? parseComma extends TempParseResult
  ? parseNum extends TempParseResult
  ? parseBrackets extends TempParseResult
  ? {
  functionName: FunctionName,
  params: [Num1, Num2],
  }: never: never: never: never : never : never;
  type res = parse<"add(11,2)">;
  对象类的体操
  体操4:实现高级类型,取出对象类型中的数字属性值
  构造对象、取属性名、取属性值的语法上文学过了,这里组合下就行:
  type filterNumberProp = {
  [Key in keyof T] : T[Key] extends number ? T[Key] : never
  }[keyof T];
  我们构造一个新的对象类型,通过 keyof 遍历对象的属性名,然后对属性值做判断,如果不是数字就返回 never,然后再取属性值。
  属性值返回 never 就代表这个属性不存在,就能达到过滤的效果。
  测试一下:
  图片
  小结:对象类型可以通过 {} 构造新对象,通过 [] 取属性值,通过 keyof 遍历属性名,综合这些语法就可以实现各种对象类型的逻辑。
  总结
  TypeScript 给 JavaScript 扩展了类型的语法,而且还支持了高级类型来生成类型。
  高级类型是通过 type 声明的带有类型参数的类型,类型参数也叫泛型。根据类型参数生成最终类型的类型计算逻辑被戏称为类型体操。
  TypeScript 的类型系统是图灵完备的,可以描述任何可计算逻辑:
  有 ? : 可以做条件判断,常配合 extends 使用
  通过递归可以实现循环
  可以做对象的构造 {}、取属性名 keyof、取属性值 T[Key]
  可以做字符串的构造 ${a}${b},字符串的模式匹配来取子串 str extends ${infer x}${infer y}
  我们分别做了这些类型体操:
  ts 实现加法:通过递归构造数组再取长度
  ts 实现重复字符串:递归构造数组来计数,然后递归构造字符串
  ts 实现 parser:通过字符串模式匹配取子串的方式来解析每一部分,最后组合调用
  ts 实现对象属性过滤:通过构造对象、取属性名、取值的语法组合调用
  其中要注意的就是数字类的要通过构造数组取长度的方式来计算,再就是字符串的模式匹配结合 infer 保存中间结果来取子串,这两个是相对难度大一些的。
  其实各种高级类型,只要熟悉了 ts 类型语法,想清楚了逻辑就能一步步写出来,和写 JS 逻辑没啥本质区别,只不过它是用于生成类型的逻辑。
  读到这里,是不是感觉高级类型的类型体操也没有啥难度了呢?

100元实现的小心愿银色珠江S201M单反相机!中国制造珠江S201系列单反照相机,应该是中国唯一一款可以更换取景器的35毫米(135胶卷)单反照相机。其中黑色机身的S201我之前曾经介绍过,而今天介绍的这款S201M则是银色机身的。这一秒化身游戏掌机,莱仕达PXNP30S上手体验每逢周末无聊的时候,我总会约上三五好友,一起开黑打游戏,像现在比较流行的王者荣耀和平精英等热门手游,都有过较深的接触,可以说,现在的手机游戏无论是游戏玩法,还是画面表现,都有着质的豪华猎装轿跑来了极氪001开启预定,空气悬架零百加速3。8秒上海国际车展前,各大品牌也蓄势待发,而吉利汽车旗下的全新纯电品牌极氪,赶在上海车展前放了一个大招,那就是极氪品牌旗下的首款量产车型极氪001开启了预定,这款车在网络上一度呼声很高,打造酷炫的光污染主机,骨伽XCBSPBARGB风扇套装RGB光污染是很多玩家从萌新走向高玩路上绕不开的一大环节,无论是外设还是装机,都要经历RGB光污染的洗礼。上次装机受预算影响,给机箱安装的是三只混光的散热风扇,没能一步到位直接搞定柠檬混动DHT全球首测,WEY玛奇朵表现如何?上海车展将亮相对于柠檬混动DHT很多人并不陌生,自长城汽车发布后,就迅速赢得了行业关注,业内人士和不少潜在消费者也对它的首款车型相当期待。4月15日,长城汽车柠檬混动DHT进行了全球首次实车测试OPPOK9怎么看电视直播?2000元性价比之王需安装第三方应用今年OPPO发布智能电视K9可以说是目前4K电视中最具性价比的电视机。1999元的OPPOK9搭载了一块55英寸4K分辨率10bit色深HDR10的大屏,且开机无广告等,这价位完全双测光模式双取景方式的理光RicohTLS401单反照相机!日本制造在我收藏的相机中,很多都是有些相对明显的特点的。今天介绍的这款日本理光公司在1970年推出的RicohTLS401,就是一台非常有特点的单反照相机该机能够提供平均值测光和点测光两种起底印度航天,要给予足够的重视!俄方将向印度出口载人航天设备印度或将实现载人飞天?航天实力不容小觑?据俄罗斯媒体报道,俄罗斯拟向印度出售载人航天设备,用于印度的航天事业,包含航天服航天座椅等。印度作为不能载人的航天大国,在航天方面可谓是野心真的有人拯救过世界吗?23分钟的坚持,这位军官避免了全球核大战从20世纪70年代开始,美国和苏联进行军事竞赛的事态越发明显。苏联在二十世纪七八十年代迅速发展军事工业,发展速度让众多西方国家感到惊讶,西方国家逐渐认识到苏联带给他们的压力。就这样幸亏普京没听拜登忽悠!美国挥舞制裁大棒,会对俄方带来哪些影响最近几年,北溪二号天然气管道项目一直是美俄博弈的重点,这一从俄罗斯直通德国的天然气管道一旦投入使用,将会给欧盟带来大量物美价廉的天然气,同样也可以给俄罗斯带来丰厚的经济回报。而正因10月24日,抗中大将被罢免,岛内接连发生三件事今天(10月24日),世界发生了很多事,土耳其突然宣布美法德等11国被列为不受欢迎的人法国前总理撰文批评马克龙但今天要和大家聊的是岛内一天时间接连发生了三件事,每件事都非同一般。第