全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2022-02-08_接近天花板的TS类型体操,看懂你就能玩转TS了

您的位置:首页 >> 新闻 >> 行业资讯

接近天花板的TS类型体操,看懂你就能玩转TS了 本文以 Typescript 4.5 及以上版本为基础,于 2022年02月07日在掘金首发 本文要实现一种类型工具 type result = Add"9007199254740991", "9007199254740991" 能计算出两个数字字符串类型的和,即 "18014398509481982" 本文代码见:https://github.com/kawayiLinLin/typescript-lodash/tree/master 中文文档:https://kawayilinlin.github.io/typescript-lodash/ 作为一名花里胡哨的前端,看到最近这不是冬奥会开始了,咱们得为运动健儿们呐喊助威,还想着能不能当一次前端届的体操运动员,和奥运健儿们一起感受在冬天运动的魅力 下面,我们就开始做操吧!(如果你不想看这么多,欢迎到评论区点在线尝试的链接去尝试一下吧) TS 类型体操基本原理if 和 else条件类型,条件类型冒号左边为 if 右边为 else typeA=1 typeB=2 typeExample=AextendsB?true:false//false type Example = A extends B ? true : false 中的 true 和 false 即可以理解成它们分别为 if 分支和 else 分支中要写的代码 而 if 中的条件即为 A extends B,A 是否可以分配给 B 要实现 else if 则需要多个这样的条件类型进行组合 模式匹配typeA=[1,2,3] typeExampleA=Aextends[inferFirst,...inferRest]?First:never//1 typeB="123" typeExampleB=Bextends`${inferFirstChar}${inferRest}`?FirstChar:never//'1' 模式匹配是我们要利用的最有用的 ts 特性之一,之后我们要实现的字符串的增删改查和元组的增删改查都要基于它 如果你想知道更多,可以参考这篇文章:模式匹配-让你 ts 类型体操水平暴增的套路 关于条件类型中 infer 的官方文档:Inferring Within Conditional Types 与或非基于条件类型可以轻松实现与或非 //C意为Condition,条件 //common //与,即C1,C2同为真 typeAndC1extendsboolean,C2extendsboolean=C1extendstrue ?C2extendstrue ?true :false :false //common //与,即C1,C2有一个为真 typeOrC1extendsboolean,C2extendsboolean=C1extendstrue ?true :C2extendstrue ?true :false //common //非,即反转C的真假状态 typeNotCextendsboolean=Cextendstrue?false:true ts 目前不支持动态个数的泛型参数,因此如果有多个条件,我们需要定义多个不同的,比如 //common //有三个条件的情况 typeAnd3C1extendsboolean,C2extendsboolean,C3extendsboolean=And AndC1,C2, C3 //common //有四个条件的情况 typeAnd4 C1extendsboolean, C2extendsboolean, C3extendsboolean, C4extendsboolean =AndAnd3C1,C2,C3,C4 现在,我们已经封装了若干个类型工具 And Or Not,要达成基于 ts 的类型系统实现加法器的目标 我们需要很多个这样的类型工具 为了方便管理,我们需要给它分模块,比如上面的与或非,我们划分在 common 里 我们还需要 function、array、number、object、string 这另外的五个,用于处理函数类型、元组类型、数字类型、对象类型、字符类型 判断相等在 js 的运算操作符中,有 == 和 === 在 ts 类型系统中,也可以实现类似的判断 //common //判断左侧类型是否可以分配给右侧类型 typeCheckLeftIsExtendsRightTextendsany,Rextendsany=TextendsR ?true :false //common //判断左侧类型是否和右侧类型一致 typeIsEqualA,B=(()=TextendsA?1:2)extends T1 ()=T1extendsB?1:2 ?true :false CheckLeftIsExtendsRight 即校验左侧类型是否可分配给右侧类型,和 == 不同的是,== 会进行类型转换后的比较,而条件类型 Left extends Right ? xxx : xxx 只会进行结构性兼容的检查 如 typeExample1={a:1;b:2}extends{a:1}?true:false//true typeExample2=1|2extends1?true:false//true 虽然两个类型长的不一样,但是可以通过约束校验 IsEqual 参考 github - typescript issue:[Feature request]type level equal operator toString要实现 ts 的数学运算过于麻烦,因为数字是无法进行 infer 的,如何判断它(一个数字类型)为整型,浮点型?还是正数,或者负数?是不是仅仅有一个数字类型没有任何办法? 这都需要基于字符类型(或元组类型)的模式匹配 //string //将类型转为字符串有一定的限制,仅支持下面的类型 typeCanStringified=string|number|bigint|boolean|null|undefined //string //将支持的类型转化为字符串 typeStringifyTextendsCanStringified=`${T}` 效果 typeExample1=Stringify0//"0" typeExample2=Stringify-1//"-1" typeExample3=Stringify0.1//"0.1" typeExample4=Stringify"0.2"//"0.2" 循环在 js 中,我们可以通过 for、while、do...while 等循环进行可迭代对象的遍历,这些都离不开一个东西,那就是循环条件 比如在 for 循环中 for (初始化; 条件; 循环后逻辑) 我们一般用一个变量 i ,每一次循环后进行自增,循环条件一般是将 i 与另一个数比较大小 那么我们还需要实现一个数字类型大小比较,数字类型累加的工具类型 ts 中的循环可以通过递归来实现,ts 的类型系统中不存在类型赋值的概念 typeExample=1 Example=2//没有这种写法 只有通过每次递归时,把当前泛型参数处理后,当做下一次递归的泛型参数,终止递归时,返回当前某一个泛型参数的类型 通过一个最简单的递归类型例子来看一下这个过程 typeExample Cextendsboolean=true, Tupleextendsunknown[]=[1] =Cextendstrue?Examplefalse,[...Tuple,1]:Tuple typeResult=Example//[1,1] //Example的两个泛型参数 如上示例,Result 得到的类型是 [1, 1] 第一次:C 是默认类型 true, 则会走到 Examplefalse, [...Tuple, 1],其中 [...Tuple, 1] 的结果为 [...[1], 1],即 [1, 1] 第二次:C 传入了 false,会走到 Tuple,Tuple 的值为上次传入的值 [1, 1],最后的返回类型为 [1, 1] 除了递归,还有两种方式可以循环,一种是分布式条件类型,还有一种是映射类型,但是它们都很难传递类型 //分布式条件类型,当泛型参数T为联合类型时,条件类型即为分布式条件类型,会将T中的每一项分别分发给extends进行比对 typeExample1=Textendsnumber?T:never typeResult1=Example1"1"|"2"|3|4//3|4 //映射类型,固定写法,in操作符会分发T成为新对象类型的键 typeExample2={ [KeyinT]:Key } typeResult2=Example2"1"|"2"|3|4//{1:2:3:4:} 基本的数学运算判断正负在部分场景下,我们可以兼容 number 类型的数字,也可以兼容 string 类型的数字,定义其为 NumberLike typeNumberLike=number|`${number}` //可分配给 NumberLike 的类型示例:number、`${number}`、1、-1、0.1、-0.1、"1"、"-1"等 判断为 0 //N意为Number数字 //number //number类型是否为0,判断N是否可分配给0|"0" typeIsZeroNextendsNumberLike=common.CheckLeftIsExtendsRightN,0|"0" //number //number类型是否大于0,泛型类型有限制NumberLike,所以它一定是个数或者由数字构成的字符串,将其转为字符串后,判断最前面是不是-,如果不是,就是大于零 typeIsOverZeroNextendsNumberLike=IsZeroextendstrue ?false :common.CheckLeftIsExtendsRight string.Stringifyextends`${"-"}${inferRest}`?Rest:never, never //number //number类型是否小于0,对上面IsOverZero的结果取反 typeIsLessZeroNextendsNumberLike=common.NotIsOverZero 两数相加在上面 循环 章节,我们讲到了,可以通过递归传递修改后的泛型参数,来创建复杂的工具类型 此场景下,我们可以生成动态的类型,最常见的有这几种,元组类型,模板字符串类型,联合类型 而元组类型的长度是可以访问的 如 [0, 1, 2]['length'] 结果为 3,且元组类型是可以拼接的,如 [...[0, 1, 2], ...[0]]['length'] 的长度为 4 那么我们可以动态生成两个指定长度的元组类型,然后拼接到一起,获取拼接后的元组长度,就可以得到正整数(和 0)的加法了 参考:https://juejin.cn/post/7050893279818317854#heading-8 //array //构造长度一定(Length)的元组 typeGetTupleLengthextendsnumber=0=GetTupleHelperLength typeGetTupleHelper Lengthextendsnumber=0, Rextendsunknown[]=[] =R["length"]extendsLength?R:GetTupleHelperLength,[...R,unknown] typeIntAddSingleHeplerN1extendsnumber,N2extendsnumber=[ ...array.GetTuple, ...array.GetTuple ]["length"] //number //正整数(和0)加法,T1,T2最大999 typeIntAddSingleN1extendsnumber,N2extendsnumber=IntAddSingleHepler N1, N2 extendsnumber ?IntAddSingleHeplerN1,N2 :number 比较大小如果想要实现元组类型的排序,那就必须要能够比较数字大小 如何实现数字类型大小的比较呢? 还是得基于元组 基于两个数 N1、 N2,创建不同的元组 T1、T2,依次减少两个元组的长度(删除第一位或最后一位),当有一个元组长度为 0 时,就是这个元组对应的数字类型,比另一个数字类型小(或相等,所以也要先判断是否不相等才进行比较) 去掉数组最后一位的实现:https://juejin.cn/post/7045536402112512007#heading-2 基于模式匹配,匹配出最后一项,和剩余项,并返回剩余项 类型系统中没有改变原类型的概念,因此元组类型的增删改查都应该直接返回修改后的类型,而不是修改后的变化值 如在 js 中,[1, 2, 3].shift() 会返回 1,[1, 2, 3].pop() 会返回 3,但是 ts 类型系统中,这样返回是没有意义的,Pop[1, 2, 3] 应该得到类型 [1, 2] //array //去掉数组的最后一位 typePopTextendsunknown[]=Textends[...inferLeftRest,inferLast] ?LeftRest :never //T意为Tuple元组 typeCompareHelper N1extendsnumber, N2extendsnumber, T1extendsunknown[]=array.GetTuple, T2extendsunknown[]=array.GetTuple =IsNotEqualN1,N2,trueextendstrue ?common.OrIsZeroT1["length"],IsZeroT2["length"]extendstrue ?IsZeroT1["length"]extendstrue ?false :true :CompareHelperarray.Pop["length"],array.Pop["length"] :false //number //比较两个数字类型大小 typeCompareN1extendsnumber,N2extendsnumber=CompareHelperN1,N2 两数相减两个数字类型相减的逻辑与两个数字类型比较大小的逻辑类似,但是返回类型时,会返回剩余长度多的元组的长度 这个实现受到元组类型长度的限制,只能得到正数(或 0),即结果的绝对值 且用在其他工具类型中时,会出现 类型实例化过深,并可能无限(Type instantiation is excessively deep and possibly infinite) 的报错,参考 github issue 即:目前有 50 个嵌套实例的限制,可以通过批处理规避限制 (20210714) //批处理示例 typeGetLettersText= Textextends`${inferC0}${inferC1}${inferC2}${inferC3}${inferC4}${inferC5}${inferC6}${inferC7}${inferC8}${inferC9}${inferRest}` ?C0|C1|C2|C3|C4|C5|C6|C7|C8|C9|GetLettersRest :Textextends`${inferC}${inferRest}` ?C|GetLettersRest :never 减法实现 typeIntMinusSingleAbsHelper N1extendsnumber, N2extendsnumber, T1extendsunknown[]=array.GetTuple, T2extendsunknown[]=array.GetTuple =IsNotEqualN1,N2,trueextendstrue ?common.OrIsZeroT1["length"],IsZeroT2["length"]extendstrue ?IsZeroT1["length"]extendstrue ?T2["length"] :T1["length"] :IntMinusSingleAbsHelperarray.Pop["length"],array.Pop["length"] :0 //number //两个数字类型相减,得到绝对值 typeIntMinusSingleAbs N1extendsnumber, N2extendsnumber =IntMinusSingleAbsHelperN1,N2 虽然有嵌套深度的限制,写好的减法不能用,但是加法是很好用的,有加法我们一样可以写出很多逻辑 参考 js 封装工具类型工具类型可能相互依赖,如果遇到没见过的,请跳转到对应章节查看 封装 string 工具类型Stringify - 将类型字符串化/** *将支持的类型转化为字符串 *@example *typeResult=Stringify//"0" */ typeStringifyTextendsCanStringified=`${T}` 原理:TS 内置的模板字符串类型 GetChars - 获取字符/** *@exports *获取模板字符串类型中的字符 *@seehttps://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html *@example *typeResult=GetChars'abc'//'a'|'b'|'c' */ typeGetChars=GetCharsHelperS,never /** *以尾递归tail-recursive的方式优化GetChars,不导出为工具类型 */ typeGetCharsHelperS,Acc=Sextends`${inferChar}${inferRest}` ?GetCharsHelperRest,Char|Acc :Acc 原理:通过模板字符串类型的模式匹配,使用 GetCharsHelper 匹配出字符串类型的第一个字符和剩余字符,然后将剩余字符继续放入 GetCharsHelper 中进行处理 每次匹配的结果通过 Acc 参数传递,当 S 为空字符串时,S 不能分配给 ${infer Char}${infer Rest},走到 false 分支中,结束递归,即返回 Acc 类型 Split - 分割字符串typeSplitHelper Sextendsstring, SplitStrextendsstring="", Textendsstring[]=[] =Sextends`${inferChar}${SplitStr}${inferRest}` ?SplitHelperRest,SplitStr,array.PushT,Char :Sextendsstring ?Sextends"" ?T :array.PushT,S :never /** *拆分字符串变为一个元组 *@example *typeResult=Split'1,2,3',','//[1,2,3] */ typeSplitSextendsstring,SplitStrextendsstring=""=SplitHelper S, SplitStr 原理:分割字符串类型,是把字符串类型转为元组类型,参数中需要设置一个元组类型用作返回结果 模板字符串类型的模式匹配是从左往右的,如果字符类型 S 为 '1,2,3',${infer Char}${','}${infer Rest} 中 Char 即为 '1',Rest 即为 '2,3',同理,如果 S 为 '2,3',${infer Char}${','}${infer Rest} 中 Char 即为 '2',Rest 即为 '3' 这样的话,我们只需要把每次匹配出的 Char 放到元组类型参数 T 中的最后一项,在匹配结束后,返回 T 的类型即可 注:array.Push 见下文 GetStringLength - 获取字符串长度/** *获取字符串的长度 *@example *typeResult=GetStringLength"123"//3 */ typeGetStringLengthSextendsstring=Split["length"] 原理:元组的长度是可以获取的,通过上文的 Split 可以将字符串类型按照 '' 分割成元组类型,再取元组的 length 即为字符串类型的长度 CharAt - 获取字符串在索引位 I 下的 字符/** *获取字符串在索引位I下的字符 *@example *typeResult=CharAt"123",1//"2" */ typeCharAtSextendsstring,Iextendsnumber=Split[I] 原理:元组类型可以进行索引访问,可以将字符串类型按照 '' 分割成元组类型,然后通过 索引访问,得到索引位 I 处的字符 Concat - 拼接两个字符串/** *拼接两个字符串 *@example *typeResult=Concat"123","456"//"123456" */ typeConcatS1extendsstring,S2extendsstring=`${S1}${S2}` 原理:TS 模板字符串类型用法 Includes - 判断字符串是否包含子串/** *判断字符串是否包含子串 *@example *typeResult=Includes"123","12"//true */ typeIncludes S1extendsstring, S2extendsstring =S1extends`${inferLeft}${S2}${inferRight}`?true:false 原理:模式匹配可判断字符串类型中是否含有子串 StartsWith - 判断字符串是否以子串为起始/** *判断字符串是否以子串为起始 *@example *typeResult=StartsWith"123","12"//true */ typeStartsWith S1extendsstring, S2extendsstring =S1extends`${S2}${inferRight}`?true:false 原理:模式匹配时,左侧不写 infer Left,代表左侧只包含空字符串,不存在任何有长度的子串,即 StartsWith EndsWith - 判断字符串是否以子串为结束/** *判断字符串是否以子串为结束 *@example *typeResult=EndsWith"123","23"//true */ typeEndsWith S1extendsstring, S2extendsstring =S1extends`${inferLeft}${S2}`?true:false 原理:模式匹配时,右侧不写 infer Right,代表右侧只包含空字符串,不存在任何有长度的子串,即 EndsWith IndexOf - 从左往右查找子串的位置typeIndexOfHelper S1extendsstring, S2extendsstring, Len1extendsnumber=GetStringLength, Len2extendsnumber=GetStringLength =common.Or number.CompareLen1,Len2, number.IsEqualLen1,Len2 extendstrue ?S1extends`${inferLeft}${S2}${inferRight}` ?GetStringLengthLeft :-1 :-1 /** *从左往右查找子串的位置 *@example *typeResult=IndexOf"123","23"//1 */ typeIndexOfS1extendsstring,S2extendsstring=IndexOfHelperS1,S2 原理:匹配出 ${infer Left}${S2}${infer Right} 中 Left,求其长度,则索引位即为 Left 的长度,如果匹配不到,返回 -1 可以先比较父串和子串的长度,如果子串比父串还长,那就不需要匹配了,直接返回 -1 LastIndexOf - 从右往左查找子串的位置typeLastIndexOfHelper S1extendsstring, S2extendsstring, Indexextendsnumber=-1/**当前从左往右匹配最大的值,匹配不到以后,上一次匹配的索引就是从右往左第一个的索引*/, AddOffsetextendsnumber=0/**每次从左往右匹配并替换成空串后,下次循序需要累加的值*/ =S1extends`${inferLeft}${S2}${inferRight}` ?LastIndexOfHelper ReplaceS1,S2,"", S2, number.IntAddSingleGetStringLengthLeft,AddOffset, number.IntAddSingleAddOffset,GetStringLength :Index /** *从右往左查找子串的位置 *@example *typeResult=LastIndexOf"23123","23"//3 */ typeLastIndexOfS1extendsstring,S2extendsstring=LastIndexOfHelper S1, S2 原理:模板字符串类型的模式匹配是从左往右的,而 LastIndexOf 是从右往左的,所以在匹配时,仍然基于从左往右匹配,但是每次匹配后,替换掉匹配过的子串为空串 然后把删掉的部分的长度累计起来,结果就是模拟从右往左匹配到的索引值 注:Replace 见下文 Replace - 在字符串中查找并替换一处子串/** *在字符串中查找并替换一处子串 *@example *typeResult=Replace"23123","23","xx"//"xx123" */ typeReplace Sextendsstring, MatchStrextendsstring, ReplaceStrextendsstring =Sextends`${inferLeft}${MatchStr}${inferRight}` ?`${Left}${ReplaceStr}${Right}` :S 原理:基于模板字符串的模式匹配,匹配到了就用 ReplaceStr 换掉 MatchStr ReplaceAll - 在字符串中查找并替换所有子串/** *在字符串中查找并替换所有子串 *@example *typeResult=Replace"23123","23","xx"//"xx1xx" */ typeReplaceAll Sextendsstring, MatchStrextendsstring, ReplaceStrextendsstring =IncludesS,MatchStrextendstrue ?ReplaceAllReplaceS,MatchStr,ReplaceStr,MatchStr,ReplaceStr :S 原理:基于 Replace,递归进行替换,替换掉所有 MatchStr,终止条件是 S 是否包含 MatchStr Repeat - 重复 Times 次数的字符串typeRepeatHelper Sextendsstring, Timesextendsnumber, OriginStrextendsstring=S, Offsetextendsnumber=1 =Timesextends0 ?"" :number.IsEqualTimes,Offsetextendstrue ?S :`${OriginStr}${RepeatHelper S, Times, OriginStr, number.IntAddSingleOffset,1 }` /** *重复Times次数的字符串 *@example *typeResult=Repeat"1",5//"11111" */ typeRepeatSextendsstring,Timesextendsnumber=1=RepeatHelperS,Times 原理:当重复次数 Times 为 0 时,直接返回空字符串 在参数中传递循环条件 Offset (每次传递时加 1,即 number.IntAddSingleOffset, 1),当循环条件 Offset 和循环次数 Times 相等时,结束递归 每次递归中,都在字符串的起始位置插入一个字符串 S,即 `${第一次的S}${`${第二次的S}${`${第三次的S}${剩下的...}`}`}` 注:number.IntAddSingle、number.IsEqual 见下文 PadStart - 在字符串前面填充typePadHelper Sextendsstring, Nextendsnumber=0, FillSextendsstring="", IsStartextendsboolean=true, Lenextendsnumber=GetStringLength, Offsetextendsnumber=Len =number.CompareN,Lenextendstrue ?number.IsEqualN,Offsetextendstrue ?S :PadHelper `${IsStartextendstrue?FillS:""}${S}${IsStartextendsfalse ?FillS :""}`, N, FillS, IsStart, Len, number.IntAddSingleOffset,1 :S /** *当字符串不满足给定的长度时,在字符串前面填充使其满足长度 *@example *typeResult=PadStart'0123',10//'0123' */ typePadStart Sextendsstring, Nextendsnumber=0, FillSextendsstring="" =PadHelperS,N,FillS 原理:比较指定的长度和当前字符串类型的长度相等,如果满足长度,直接返回 S,每次递归时,给 S 左侧添加指定的字符,直到 S 的长度满足指定的长度时,终止递归 PadEnd - 在字符串后面填充/** *当字符串不满足给定的长度时,在字符串后面填充使其满足长度 *@example *typeResult=PadStart'0123',10//'0123' */ typePadEnd Sextendsstring, Nextendsnumber=0, FillSextendsstring="" =PadHelperS,N,FillS,false 原理:比较给定的长度和当前字符串类型的长度相等,如果满足长度,直接返回 S,每次递归时,给 S 右侧添加指定的字符,直到 S 的长度满足给定的长度时,终止递归 TrimLeft - 去掉字符串前面的空格/** *去掉字符串类型左侧的空格 *@seehttps://juejin.cn/post/7045536402112512007#heading-5 *@example *typeResult=PadStart'0123'//'0123' */ typeTrimLeftSextendsstring=Sextends`${ |"" |"\t" |"\n"}${inferRightRest}` ?TrimLeftRightRest :S 原理:每次匹配 ${一个空格}${剩余字符} 然后让 剩余字符 继续匹配,直到不符合 ${一个空格}${剩余字符} 的规则时,终止递归,返回 S TrimRight - 去掉字符串后面的空格/** *去掉字符串类型右侧的空格 *@example *typeResult=PadStart'0123'//'0123' */ typeTrimRightSextendsstring=Sextends`${inferLeftRest}${ |"" |"\t" |"\n"}` ?TrimRightLeftRest :S 原理:每次匹配 ${剩余字符}${一个空格} 然后让 剩余字符 继续匹配,直到不符合 ${剩余字符}${一个空格} 的规则时,终止递归,返回 S Trim - 去掉字符串的空格/** *去掉字符串类型两侧的空格 *@example *typeResult=PadStart'0123'//'0123' */ typeTrimSextendsstring=TrimLeftTrimRight 原理:先用 TrimRight 去掉右侧的空格,再把前者结果交给 TrimLeft 去掉左侧的空格 ToUpperCase - 字符串转大写/** *字符串转大写 *@example *typeResult=ToUpperCase'abc'//'ABC' */ typeToUpperCaseSextendsstring=Uppercase 原理:TS 内置 ToLowerCase - 字符串转小写/** *字符串转小写 *@example *typeResult=ToUpperCase'ABC'//'abc' */ typeToLowerCaseSextendsstring=Lowercase 原理:TS 内置 SubString - 截取 start(包括)到 end(不包括)之间的字符串typeSubStringHelper Sextendsstring, Startextendsnumber, Endextendsnumber, Offsetextendsnumber=0, Cacheextendsstring[]=[] =number.IsEqualOffset,Endextendstrue ?array.JoinCache,"" :SubStringHelper S, Start, End, number.IntAddSingleOffset,1, common.And3 common.Ornumber.CompareOffset,Start,number.IsEqualOffset,Start, common.Ornumber.CompareEnd,Offset,number.IsEqualOffset,End, CharAtS,Offsetextendsstring?true:false extendstrue ?array.PushCache,CharAtS,Offset :Cache /** *截取start(包括)到end(不包括)之间的字符串 *@example *typeResult=SubString'123',0,1//'1' */ typeSubString Sextendsstring, Startextendsnumber, Endextendsnumber =SubStringHelperS,Start,End 原理:遍历字符串类型的每一个字符,如果当前索引大于等于 Start,并且小于等于 End,就把当前字符 push 到元组中,最后用 array.Join,将元组转为字符串类型 注:array.Join 见下文 SubStr - 在字符串中抽取从开始下标到结束下标的字符/** *在字符串中抽取从开始下标开始的指定数目的字符 *@example *typeResult=SubStr'123',1,2//'23' */ typeSubStr Sextendsstring, Startextendsnumber, Lenextendsnumber =SubStringHelperS,Start,number.IntAddSingleStart,Len 原理:SubString 需要起始和结束,有 Start 和 Len 就可以先算出 End,就可以使用 SubString 了 封装 array 工具类型GetTuple - 构造指定长度的元组/** *构造长度一定(Length)的元组 *@example *typeResult=GetTuple//[unknown,unknown,unknown] */ typeGetTupleLengthextendsnumber=0=GetTupleHelperLength typeGetTupleHelper Lengthextendsnumber=0, Rextendsunknown[]=[] =R["length"]extendsLength?R:GetTupleHelperLength,[...R,unknown] ArraySet - 更改元组中指定索引位的类型typeSetHelper Textendsunknown[], Indexextendsnumber, Value, Offsetextendsnumber=0, Cacheextendsunknown[]=[] =OffsetextendsT["length"] ?Cache :SetHelper T, Index, Value, number.IntAddSingleOffset,1, PushCache,OffsetextendsIndex?Value:T[Offset] /** *更改元组中指定索引位的类型 *@example *typeResult=ArraySet[1,2,3],2,4//[1,2,4] */ typeArraySetTextendsunknown[],Indexextendsnumber,Value=SetHelper T, Index, Value 原理:遍历元组类型,如果 Offset 等于给定的索引,则该索引对应的类型替换为给定的类型,否则用原类型 TupleToUnion - 从元(数)组类型构造联合类型/** *从元(数)组类型构造联合类型 *@example *typeResult=TupleToUnion[1,2,3]//1|2|3 */ typeTupleToUnionTextendsunknown[]=T[number] 原理:元组(数组)类型的索引访问会得到联合类型 Pop - 去除元组类型的最后一位/** *去掉元组的最后一位 *@seehttps://juejin.cn/post/7045536402112512007#heading-2 *@example *typeResult=Pop[1,2,3]//[1,2] */ typePopTextendsunknown[]=Textends[...inferLeftRest,inferLast] ?LeftRest :never 原理:基于元组的模式匹配,提取最后一项,返回剩余项 Shift - 去除元组类型的第一位/** *去掉数组的第一位 *@example *typeResult=Shift[1,2,3]//[2,3] */ typeShiftTextendsunknown[]=Textends[inferFirst,...inferRightRest] ?RightRest :never 原理:与 Pop 同理 UnShift - 在元组前面插入一位/** *在元组前面插入一位 *@example *typeResult=UnShift[1,2,3],0//[0,1,2,3] */ typeUnShiftTextendsunknown[],Item=[Item,...T] 原理:[] 中直接写类型可以构建新元组类型,其中写 ...Tuple,与 js 中的扩展运算符效果一致 Push - 在元组后面插入一位/** *在元组最后插入一位 *@example *typeResult=Push[1,2,3],4//[1,2,3,4] */ typePushTextendsunknown[],Item=[...T,Item] 原理:同 UnShift Concat - 合并两个元组类型/** *合并两个元组类型 *@example *typeResult=Concat[1,2,3],[4]//[1,2,3,4] */ typeConcatTextendsunknown[],Rextendsunknown[]=[...T,...R] 原理:见 UnShift Join - 将元组类型拼接成字符串类型/** *将元组类型拼接成字符串类型 *@example *typeResult=Join[1,2,3]//"1,2,3" */ typeJoin Textendsstring.CanStringified[], SplitStrextendsstring.CanStringified="" =T["length"]extends0 ?"" :Textends[inferLeft,...inferRightRest] ?Leftextendsstring.CanStringified ?RightRestextendsstring.CanStringified[] ?`${Left}${T["length"]extends1?"":SplitStr}${Join RightRest, SplitStr }` :never :never :never 原理:每次递归时提取元组第一个类型,然后将此类型放到模板字符串类型的第一个位置 ${第一个位置}${第二个位置}${第三个位置} 第二个位置即转为字符串用来分隔的子串,如果元组的长度为 0,则为空串 第三个位置则是剩下部分的逻辑,即重复最开始的逻辑 Every - 校验元组中每个类型是否都符合条件typeEveryHelper Textendsunknown[], Check, Offsetextendsnumber=0, CacheBoolextendsboolean=true =T["length"]extendsOffset ?CacheBool :EveryHelper T, Check, number.IntAddSingleOffset,1, common.Andcommon.CheckLeftIsExtendsRightT[Offset],Check,CacheBool /** *校验元组中每个类型是否都符合条件 *@example *typeResult=Every[1,2,3],number//true */ typeEveryTextendsunknown[],Check=T["length"]extends0 ?false :EveryHelperT,Check 原理:初始类型 CacheBool 为 true,依次将元组中每个类型与初始类型进行 与 操作,如果元组长度为 0,则返回 false 注:common.And、common.CheckLeftIsExtendsRight 见下文 Some - 校验元组中是否有类型符合条件typeSomeHelper Textendsunknown[], Check, Offsetextendsnumber=0, CacheBoolextendsboolean=false =T["length"]extendsOffset ?CacheBool :SomeHelper T, Check, number.IntAddSingleOffset,1, common.Orcommon.CheckLeftIsExtendsRightT[Offset],Check,CacheBool /** *校验元组中是否有类型符合条件 *@example *typeResult=Every['1','2',3],number//true */ typeSomeTextendsunknown[],Check=SomeHelperT,Check 原理:初始类型 CacheBool 为 false,依次将元组中每个类型与初始类型进行 或 操作,如果元组长度为 0,则返回 false 注:common.Or、common.CheckLeftIsExtendsRight 见下文 Fill - 以指定类型填充元组类型typeFillHelper Textendsunknown[], F, Offsetextendsnumber=0 =T["length"]extends0 ?F[] :OffsetextendsT["length"] ?common.IsEqualT,F[]extendstrue/**any[]-T[]*/ ?T :F[] :FillHelperarray.Pusharray.Shift,F,F,number.IntAddSingleOffset,1 /** *以指定类型填充元组类型 *@example *typeResult=Fill['1','2',3,any],1//[1,1,1,1] */ typeFillTextendsunknown[],F=undefined=FillHelperT,F 原理:如果原元组长度为 0,则直接返回由新类型构成的元组 F[] 如果是数组类型如:any[]、never[]、number[],也应该直接替换成 T[] 否则,每次在原元组中删除第一个,然后在最前面添加一个新类型,直到循环条件与 T 的长度一致时,终止递归 注:commom.IsEqual 见下文 Filter - 过滤出元组类型中符合条件的类型typeFilterHelper Textendsunknown[], C, Strictextendsboolean, Offsetextendsnumber=0, Cacheextendsunknown[]=[] =OffsetextendsT["length"] ?Cache :FilterHelper T, C, Strict, number.IntAddSingleOffset,1, common.AndStrict,common.IsEqualT[Offset],Cextendstrue ?array.PushCache,T[Offset] :common.And common.NotStrict, common.CheckLeftIsExtendsRightT[Offset],C extendstrue ?array.PushCache,T[Offset] :Cache /** *过滤出元组类型中符合条件的类型 *@example *typeResult=Filter['1','2',3,any,1],1,true//[1] */ typeFilter Textendsunknown[], C, Strictextendsboolean=false =FilterHelperT,C,Strict 原理:严格模式,即 any 只能为 any,而不能为 1、unknown 这样的其他类型 如果是严格模式就用 common.IsEqual 进行约束校验,否则用 common.CheckLeftIsExtendsRight 进行约束校验 每次递归时,如果满足上述条件,则放入新的元组类型中 如果循环条件 Offset 等于 T 的长度是,终止循环,返回新的元组类型 Cache 注:common.Not 见下文 MapWidthIndex - 将元组类型映射为带索引的元组类型interfaceIndexMappedItemItem,Indexextendsnumber,Tupleextendsunknown[]{ item:Item index:Index tuple:Tuple } typeMapWidthIndexHelper Textendsunknown[], Offsetextendsnumber=0, Cacheextendsunknown[]=[] =T["length"]extendsOffset ?Cache :MapWidthIndexHelper T, number.IntAddSingleOffset,1, PushCache,IndexMappedItemT[Offset],Offset,T /** *将元组类型映射为带索引的元组类型 *@example *typeResult=MapWidthIndex[1,2]//[{item:index:tuple:[1,},{item:index:tuple:[1,}] */ typeMapWidthIndexTextendsunknown[]=MapWidthIndexHelper 原理:声明一个接口用于构造新的元组类型中的项,然后每次递归都向 Cache 中添加一个经过 IndexMappedItem 处理后的类型 注:由于 TS 中实现不了回调的效果,因为带泛型参数的工具类型不能直接当做类型传递,必须要先传了泛型参数才能用,所以暂时无法实现 js 中 Array.prototype.map 的效果 Find - 找到元组类型中第一个符合条件的类型typeFindHelper Textendsunknown[], C, Offsetextendsnumber=0 =Offsetextendsnumber.IntAddSingleT["length"],1 ?null :common.CheckLeftIsExtendsRightT[Offset],Cextendstrue ?T[Offset] :FindHelperT,C,number.IntAddSingleOffset,1 /***/ typeFindTextendsunknown[],C=FindHelperT,C 原理:遍历在元组中找,如果找到了匹配的,则返回该类型,否则返回 null 类型 Reverse - 反转元组typeReverseHelper Textendsunknown[], Offsetextendsnumber=0, Cacheextendsunknown[]=[] =Cache["length"]extendsT["length"] ?Cache :ReverseHelperT,number.IntAddSingleOffset,1,UnShiftCache,T[Offset] /***/ typeReverseTextendsunknown[]=ReverseHelper 原理:遍历老元组类型,每次在新元组类型 Cache 的前面插入当前类型 FindLast - 找到元组类型中最后一个符合条件的类型/***/ typeFindLastTextendsunknown[],C=FindReverse,C 原理:反转老元组类型,然后通过 Find 查找 FindIndex - 找到元组类型中第一个符合条件的类型的索引typeFindIndexHelper Textendsunknown[], C, Strictextendsboolean=false, Offsetextendsnumber=0 =Offsetextendsnumber.IntAddSingleT["length"],1 ?-1 :common.Andcommon.IsEqualT[Offset],C,Strictextendstrue ?Offset :common.And common.CheckLeftIsExtendsRightT[Offset],C, common.NotStrict extendstrue ?Offset :FindIndexHelperT,C,Strict,number.IntAddSingleOffset,1 /***/ typeFindIndex Textendsunknown[], C, Strictextendsboolean=false =FindIndexHelperT,C,Strict 原理:严格模式,参考上文 Filter,遍历元组,符合约束校验时,返回当前 Offset,否则结束后返回 -1 FindLastIndex - 找到元组类型中最后一个符合条件的类型的索引typeFindLastIndexHelper Textendsunknown[], C, Item=FindReverseMapWidthIndex,IndexMappedItemC,number,T =ItemextendsIndexMappedItemC,number,T?Item["index"]:-1 typeFindLastIndexTextendsunknown[],C=FindLastIndexHelperT,C 原理:通过 MapWidthIndex 将索引记录到元组的每个类型中,使用 Find 匹配反转后的元组,匹配到时,返回该类型的 Item['index'] 值即为结果 Flat - 扁平化元组typeFlatHelper Textendsunknown[], Offsetextendsnumber=0, Cacheextendsunknown[]=[] =OffsetextendsT["length"] ?Cache :FlatHelper T, number.IntAddSingleOffset,1, T[Offset]extendsunknown[] ?ConcatCache,T[Offset] :PushCache,T[Offset] typeFlatTextendsunknown[]=FlatHelper 原理:遍历元组类型,如果当前类型不满足 unknown[] 的约束,则将它 Push 进新元组中,否则将它 Concat 进去 Includes - 元组类型中是否存在一个符合条件的类型typeIncludesTextendsunknown[],C=common.CheckLeftIsExtendsRight C, TupleToUnion 原理:将元组转为联合类型,如果约束条件 C 可以分配给该联合类型,那么就是 true Slice - 提取元组类型中指定起始位置到指定结束位置的类型构造新元组类型typeSliceHelper Textendsunknown[], Startextendsnumber, Endextendsnumber, Offsetextendsnumber=0, Cacheextendsunknown[]=[] =number.IsEqualOffset,Endextendstrue ?Cache :SliceHelper T, Start, End, number.IntAddSingleOffset,1, common.And3 common.Ornumber.CompareOffset,Start,number.IsEqualOffset,Start, common.Ornumber.CompareEnd,Offset,number.IsEqualOffset,End, common.Or number.CompareT["length"],Offset, number.IsEqualT["length"],End extendstrue ?array.PushCache,T[Offset] :Cache typeSlice Textendsunknown[], Startextendsnumber, Endextendsnumber =SliceHelperT,Start,End 原理:和字符串裁剪的类似,遍历老元组,当循环条件 Offset 大于等于 Start 或 小于等于 End 时,将这些类型 Push 到新元组中 Sort - 排序typeSortHepler2 Textendsnumber[], Offsetextendsnumber=0, Offset1extendsnumber=0, Offset1Addedextendsnumber=number.IntAddSingleOffset1,1, Seted1extendsunknown[]=ArraySetT,Offset1Added,T[Offset1], Seted2extendsunknown[]=ArraySetSeted1,Offset1,T[Offset1Added] =number.IntAddSingle number.IntAddSingleOffset,Offset1, 1 extendsT["length"] ?SortHepler1T,number.IntAddSingleOffset,1 :SortHepler2 number.CompareT[Offset1],T[Offset1Added]extendstrue ?Seted2extendsnumber[] ?Seted2 :never :T, number.IntAddSingleOffset1,1 typeSortHepler1 Textendsnumber[], Offsetextendsnumber=0 =OffsetextendsT["length"]?T:SortHepler2T,Offset typeSortTextendsnumber[]=SortHepler1 原理:最简单的冒泡排序,每次排序时,将大的与小的置换 注:受到嵌套实例深度的限制,只能排两个类型的元组 封装 number 工具类型IsZero - 判断类型为 0/** *number类型是否为0 *@example *typeResult=IsZero//true */ typeIsZeroNextendsNumberLike=common.CheckLeftIsExtendsRightN,0|"0" 注:原理见上文,NumberLike 见上文 IsOverZero - 是否大于 0/** *number类型是否大于0 *@example *typeResult=IsOverZero//true */ typeIsOverZeroNextendsNumberLike=IsZeroextendstrue ?false :common.CheckLeftIsExtendsRight string.Stringifyextends`${"-"}${inferRest}`?Rest:false, false 注:原理见上文 IsLessZero - 是否小于 0/** *number类型是否小于0 *@example *typeResult=IsLessZero//true */ typeIsLessZeroNextendsNumberLike=common.NotIsOverZero IsFloat - 是否为浮点型/** *number类型是否是小数 *@example *typeResult=IsFloat1.2//true */ typeIsFloat NextendsNumberLike, OnlyCheckPointextendsboolean=true =string.Stringifyextends`${inferLeft}${"."}${inferRight}` ?OnlyCheckPointextendstrue ?true :common.Notarray.Everystring.SplitRight,"0" :false 原理:转为字符串类型后判断是否小数点 IsInt - 是否为整型/** *number类型是否是整数 *@example *typeResult=IsInt//true */ typeIsInt NextendsNumberLike, OnlyCheckPointextendsboolean=true =common.NotIsFloatN,OnlyCheckPoint 原理:IsFloat 取反 IsEqual - 数字类型是否相等/** *两个number类型是否相等 *@example *typeResult=IsEqual1,1//true */ typeIsEqual LextendsNumberLike, RextendsNumberLike, Strictextendsboolean=true =Strictextendstrue ?common.CheckLeftIsExtendsRightL,R :common.CheckLeftIsExtendsRightstring.Stringify,string.Stringify 原理:见上文 IsNotEqual - 数字类型是否不相等/** *两个number类型是否不相等 *@example *typeResult=IsNotEqual1,2//true */ typeIsNotEqual LextendsNumberLike, RextendsNumberLike, Strictextendsboolean=true =common.NotIsEqualL,R,Strict 原理:IsEqual 取反 IntAddSingle - 整数相加typeIntAddSingleHeplerN1extendsnumber,N2extendsnumber=[ ...array.GetTuple, ...array.GetTuple ]["length"] /** *正整数(和0)加法,A1,A2最大999 *@seehttps://juejin.cn/post/7050893279818317854#heading-8 *@example *typeResult=IntAddSingle1,2//3 */ typeIntAddSingleN1extendsnumber,N2extendsnumber=IntAddSingleHepler N1, N2 extendsnumber ?IntAddSingleHeplerN1,N2 :number 原理:见上文 Compare - 比较大小typeCompareHelper N1extendsnumber, N2extendsnumber, A1extendsunknown[]=array.GetTuple, A2extendsunknown[]=array.GetTuple =IsNotEqualN1,N2,trueextendstrue ?common.OrIsZeroA1["length"],IsZeroA2["length"]extendstrue ?IsZeroA1["length"]extendstrue ?false :true :CompareHelperarray.Pop["length"],array.Pop["length"] :false /** *比较大小 *@example *typeResult=Compare1,2//false */ typeCompareN1extendsnumber,N2extendsnumber=CompareHelperN1,N2 原理:见上文 IntMinusSingleAbs - 两数相减typeIntMinusSingleAbsHelper N1extendsnumber, N2extendsnumber, A1extendsunknown[]=array.GetTuple, A2extendsunknown[]=array.GetTuple =IsNotEqualN1,N2,trueextendstrue ?common.OrIsZeroA1["length"],IsZeroA2["length"]extendstrue ?IsZeroA1["length"]extendstrue ?A2["length"] :A1["length"] :IntMinusSingleAbsHelperarray.Pop["length"],array.Pop["length"] :0 /** *两数相减 *@example *typeResult=IntMinusSingleAbs2,1//1 */ typeIntMinusSingleAbs N1extendsnumber, N2extendsnumber =IntMinusSingleAbsHelperN1,N2 原理:见上文 GetHalf - 获取当前数字类型的一半typeGetHalfHelperNextendsnumber,Offsetextendsnumber=0=IsEqual IntAddSingleOffset,Offset, N extendstrue ?Offset :IsEqualIntAddSingleIntAddSingleOffset,Offset,1,Nextendstrue ?IntAddSingleOffset,1 :GetHalfHelperN,IntAddSingleOffset,1 /** *获取当前数字类型的一半 *@example *typeResult=GetHalf//2 */ typeGetHalfNextendsnumber=GetHalfHelper 原理:循环,当 Offset + Offset 等于 N 时,或 Offset + 1 + Offset 等于 N 时,Offset 即为结果 ToNumber - 字符串类型转数字类型/**@seehttps://juejin.cn/post/6999280101556748295#heading-68*/ typeMap={ "0":[] "1":[1] "2":[...Map["1"],1] "3":[...Map["2"],1] "4":[...Map["3"],1] "5":[...Map["4"],1] "6":[...Map["5"],1] "7":[...Map["6"],1] "8":[...Map["7"],1] "9":[...Map["8"],1] } typeMake10ArrayTextendsany[]=[ ...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T ] typeToNumberHelper Sextendsstring, Lextendsany[]=[] =Sextends`${inferF}${inferR}` ?ToNumberHelper R, [...Make10Array,...(FextendskeyofMap?Map[F]:never)] :L["length"] /** *字符串类型转数字类型 *@example *typeResult=ToNumber"100"//100 */ typeToNumberSextendsstring=ToNumberHelper 原理:创建一个字符与元组的映射表,我们通过前文的探究已知数字类型可由元组的长度得到,那么就根据每个字符依次构建不同长度的元组即可得到结果 Make10Array 是将上一次的结果 * 10 见 https://juejin.cn/post/6999280101556748295#heading-68 Add - 数字相加见下文 封装 object 工具类型KeysToUnion - 对象类型的所有键转联合类型typeKeysToUnion=keyofT Values - 获取对象类型的值构成的联合类型typeValues=T[KeysToUnion] KeysToTuple - 获取对象类型键够成的元组类型typeKeysToTuple=KeysToUnion[] ExtractValues - 过滤出符合类型 V 的属性typeExtractValuesT,V={ [KeyinkeyofTasT[Key]extendsV?Key:never]:T[Key] } ExcludeValues - 过滤出不符合类型 V 的属性typeExcludeValuesT,V={ [KeyinkeyofTasT[Key]extendsV?never:Key]:T[Key] } GetterSetterPrefix - 向对象类型中添加 get 和 set 前缀typeGetterSetterPrefix={ [KeyinkeyofTasKeyextendsstring?`get${CapitalizeKey}`:never]:{ ():T[Key] } }{ [KeyinkeyofTasKeyextendsstring?`set${CapitalizeKey}`:never]:{ (val:T[Key]):void } }T Proxify - 将对象类型的每个属性值转为 get 和 set 形式typeProxify={ [PinkeyofT]:{ get():T[P] set(v:T[P]):void } } NullableValue - 将对象类型的每个属性值转为可为空的typeNullableValue={ [KeyinkeyofT]?:common.NullableT[Key] } Include - 提取出符合类型 U 的键名构造新的对象类型typeIncludeTextendsobject,Uextendskeyofany={ [KeyinkeyofTasKeyextendsU?Key:never]:T[Key] } ChangeRecordType - 将对象类型的属性值填充为类型 TtypeChangeRecordTypeK,T=undefined={ [PinkeyofK]?:T } Mutable - 变为可写类型typeMutable={ -readonly[PinkeyofT]:T[P] } ReadonlyPartial - 变为只读且可选的typeReadonlyPartial={ readonly[PinkeyofT]?:T[P] } DeepPartial - 将对象类型的所有属性转为可选typeDeepPartial={ [KeyinkeyofT]?:T[Key]extendsobject?DeepPartialT[Key]:T[Key] } ChainedAccessUnion - 查找对象类型的所有路径typeChainedAccessUnionTextendsobject=ChainedAccessUnionHelper typeChainedAccessUnionHelper T, A={ [KeyinkeyofT]:T[Key]extendsstring?never:T[Key] }, B={ [KeyinkeyofA]:A[Key]extendsnever ?never :A[Key]extendsobject ? |`${ExtractKey,string}.${ExtractkeyofA[Key],string}` |(ChainedAccessUnionHelperA[Key]extendsinferU ?`${ExtractKey,string}.${ExtractU,string}` :never) :never } =Textendsobject ?ExcludekeyofA|ExcludeValues,never,never :never 封装 common 工具类型Not - 非typeNotCextendsboolean=Cextendstrue?false:true And - 与typeAndC1extendsboolean,C2extendsboolean=C1extendstrue ?C2extendstrue ?true :false :false Or - 或typeOrC1extendsboolean,C2extendsboolean=C1extendstrue ?true :C2extendstrue ?true :false CheckLeftIsExtendsRight - 约束校验typeCheckLeftIsExtendsRightTextendsany,Rextendsany=TextendsR ?true :false IsEqual - 类型严格相等/** *https://github.com/microsoft/TypeScript/issues/27024#issuecomment-510924206 */ typeIsEqualA,B=(()=TextendsA?1:2)extends T1 ()=T1extendsB?1:2 ?true :false IsAny - 类型是否为 anytypeIsAny=0extends(1T)?true:false Diff - 差异typeDiffT,C=ExcludeT,C|ExcludeC,T SumAggregate - 并集typeSumAggregateT,U=T|U Nullable - 可为空typeNullable=T|null|undefined 封装 function 工具类型Noop - 普通函数类型typeNoop=(...args:any)=any GetAsyncFunctionReturnType - 获取异步函数返回值typeGetAsyncFunctionReturnTypeFextendsNoop=AwaitedReturnType GetFunctionLength - 获取参数长度typeGetFunctionLengthFextendsNoop=Fextends(...args:inferP)=any ?P["length"] :never 实现一个加法器!分析让我们回忆一下,在小学三年级的数学计算中,10进制的小数相加要怎么做? 如 1.8 + 1.52 是不是要先把小数点对齐 1.8 + 1.52 —————— 2.32 然后从右往左依次计算,如果当前位的结果大于等于 10,则需要往左边进一位 那么 ts 要如何知道 1 + 1 = 2,1 + 2 = 3 呢? 我们可以定义一个映射表,二维元组,用索引访问的方式得到一位整数加法的结果和它的进位情况 实现什么是一个形如数字的字符串类型如 "0.1"、"1" 即是,用 ts 表示即 ${number} 但是这样的数字也是符合这个条件的:"000.1" 如果想限制这样的数,用正则表达式很好处理,如何在 ts 类型系统中限制呢? 我们可以定义除了小数点前面有多个零的情况的类型 //每位数 typeNumbers=0|1|2|3|4|5|6|7|8|9 //开头不能是多个0 typeAdvancedNumericCharacters= |`${0}.${number}` |`${ExcludeNumbers,0}${number|""}.${number}` |`${ExcludeNumbers,0}${Numbers|""}.${number}` |`${ExcludeNumbers,0}${number}` |`${Numbers}` 定义加法表我们如果是 1 + 1,且加法表为 AddMap,我们想这样使用 AddMap[1][1],得到相加的结果和需要进位的结果 即: typeAddMap=[ [/*0+0*/{result:0,add:0},/*0+1*/{/*...*/}/*...*/] //... ] 数据处理小学三年级数学中的加法,要从右往左算,要以小数点对齐 但是在上文中我们实现的字符串或元组处理工具中,从右往左的都很麻烦,所以从左往右更简单,且元组操作要比字符串操作更方便,那么在实际的加法运算中 我们真实处理的数据应该是一个被反转的元组 按小数点对齐即按小数点分割,然后用 PadStart 和 PadEnd 补 0 //typeResult=SplitByPoint"1.02"//["1","02"] //如果没有小数点,则小数位补0 typeSplitByPointSextendsAdvancedNumericCharacters=string.Includes S, "." extendstrue ?string.SplitS,"." :[S,"0"] //typeResult=AddHelperSplitToArr"1.02","0.123"//[["1","02"],["10","123"]] //这里需要一起分割两个数字嘛 typeAddHelperSplitToArr S1extendsAdvancedNumericCharacters, S2extendsAdvancedNumericCharacters, Result=[SplitByPoint,SplitByPoint] =Resultextends[[`${number}`,`${number}`],[`${number}`,`${number}`]] ?Result :never //typeResult=AddFillZeroHelper[["1","02"],["10","123"]]//[["01","020"],["10","123"]] //对上面的结果用PadStart和PadEnd补0 typeAddFillZeroHelper Dataextends[[`${number}`,`${number}`],[`${number}`,`${number}`]], Result=[ [ string.PadStartData[0][0],string.GetStringLengthData[1][0],"0", string.PadEndData[0][1],string.GetStringLengthData[1][1],"0" ], [ string.PadStartData[1][0],string.GetStringLengthData[0][0],"0", string.PadEndData[1][1],string.GetStringLengthData[0][1],"0" ] ] =Resultextends[[`${number}`,`${number}`],[`${number}`,`${number}`]] ?Result :never 转元组后反转方便从左往右计算//typeResult=AddFillZeroHelper[["1","02"],["10","123"]] //[[["1","0"],["0","2","0"]],[["0","1"],["3","2","1"]]] typeAddReverseData Dataextends[[`${number}`,`${number}`],[`${number}`,`${number}`]], Result=[ [ array.Reversestring.SplitData[0][0], array.Reversestring.SplitData[0][1] ], [ array.Reversestring.SplitData[1][0], array.Reversestring.SplitData[1][1] ] ] =Resultextends[ [`${Numbers}`[],`${Numbers}`[]], [`${Numbers}`[],`${Numbers}`[]] ] ?Result :never 开始计算单独计算小数位或整数位,减少复杂度,如果有进位,就在元组的最前面添加 "10" typeStepAdderHelper DataLeftextends`${Numbers}`[],//整数部分 DataRightextends`${Numbers}`[],//小数部分 Curryextends`${Numbers}`=`${0}`,//当前是否有进位 Offsetextendsnumber=0,//循环的偏移量 ResultCacheextends`${number}`[]=[],//用于缓存结果 NextOffsetextendsnumber=number.IntAddSingleOffset,1,//偏移量加1 CurrentextendsAddMap[Numbers][Numbers]=AddMap[DataLeft[Offset]][DataRight[Offset]],//当前的结果 CurrentWidthPreCurryextends`${Numbers}`=AddMap[Current["result"]][Curry]["result"]//当前的实际结果(加上进位) =DataLeft["length"]extendsDataRight["length"] ?`${Offset}`extends`${DataLeft["length"]}` ?ResultCache :StepAdderHelper DataLeft, DataRight, Current["add"], NextOffset, common.And number.IsEqualCurrent["add"],"1", number.IsEqual`${NextOffset}`,`${DataLeft["length"]}` extendstrue ?array.Push["10",...ResultCache],CurrentWidthPreCurry :array.PushResultCache,CurrentWidthPreCurry :never 拼接结果typeNumbersWidthCurry=Numbers|10 typeMergeResultHelper Dataextends[ [`${Numbers}`[],`${Numbers}`[]], [`${Numbers}`[],`${Numbers}`[]] ],//处理后的的数据 LeftIntextends`${Numbers}`[]=Data[0][0],//加数的整数部分 LeftFloatextends`${Numbers}`[]=Data[0][1],//加数的小数部分 RightIntextends`${Numbers}`[]=Data[1][0],//被加数的整数部分 RightFloatextends`${Numbers}`[]=Data[1][1],//被加数的小数部分 FloatAddedextends`${NumbersWidthCurry}`[]=StepAdderHelper LeftFloat, RightFloat ,//小数部分加法,附带进位的反序元组的结果 FloatHasCurryextendsboolean=FloatAdded[0]extends"10"?true:false,//小数是否有进位 DeleteCurryFloatResultextendsunknown[]=FloatHasCurryextendstrue ?array.ShiftFloatAdded :FloatAdded,//小数部分删除进位后的可以直接用的反序元组结果 IntAddedextends`${NumbersWidthCurry}`[]=StepAdderHelper LeftInt, RightInt, FloatHasCurryextendstrue?`1`:"0" ,//整数部分加法,初始会附带上小数部分的进位,结果会带上自己的进位 IntHasCurryextendsboolean=IntAdded[0]extends"10"?true:false,//整数部分是否还有进位 DeleteCurryIntResultextendsunknown[]=IntHasCurryextendstrue ?array.ShiftIntAdded :IntAdded,//整数部分删除进位后的可以直接用的反序元组结果 ResultReversed=array.Reverse LeftFloat["length"]extends0 ?DeleteCurryIntResult :array.Concat [...DeleteCurryFloatResult,"."], [...DeleteCurryIntResult] ,//将整数小数(小数点)加入结果,并将反序的元组还原 FloatResult=array.Join ResultReversedextendsstring[] ?IntHasCurryextendstrue ?["1",...ResultReversed] :ResultReversed :never, "" //转字符串,并处理整数的进位 =FloatResult //最终结果 typeAdd S1extendsAdvancedNumericCharacters, S2extendsAdvancedNumericCharacters =MergeResultHelper AddReverseDataAddFillZeroHelperAddHelperSplitToArrS1,S2 最终代码typeNumbers=0|1|2|3|4|5|6|7|8|9 typeAdvancedNumericCharacters= |`${0}.${number}` |`${ExcludeNumbers,0}${number|""}.${number}` typeAddMap=[ [ {result:"0";add:"0"},//00 {result:"1";add:"0"},//01 {result:"2";add:"0"},//02 {result:"3";add:"0"},//03 {result:"4";add:"0"},//04 {result:"5";add:"0"},//05 {result:"6";add:"0"},//06 {result:"7";add:"0"},//07 {result:"8";add:"0"},//08 {result:"9";add:"0"}//09 ], [ {result:"1";add:"0"},//10 {result:"2";add:"0"},//11 {result:"3";add:"0"},//12 {result:"4";add:"0"},//13 {result:"5";add:"0"},//14 {result:"6";add:"0"},//15 {result:"7";add:"0"},//16 {result:"8";add:"0"},//17 {result:"9";add:"0"},//18 {result:"0";add:"1"}//19 ], //.... [ {result:"8";add:"0"},//80 {result:"9";add:"0"},//81 {result:"0";add:"1"},//82 {result:"1";add:"1"},//83 {result:"2";add:"1"},//84 {result:"3";add:"1"},//85 {result:"4";add:"1"},//86 {result:"5";add:"1"},//87 {result:"6";add:"1"},//88 {result:"7";add:"1"}//89 ], [ {result:"9";add:"0"},//90 {result:"0";add:"0"},//91 {result:"1";add:"1"},//92 {result:"2";add:"1"},//93 {result:"3";add:"1"},//94 {result:"4";add:"1"},//95 {result:"5";add:"1"},//96 {result:"6";add:"1"},//97 {result:"7";add:"1"},//98 {result:"8";add:"1"}//99 ] ] typeSplitByPointSextendsAdvancedNumericCharacters=string.Includes S, "." extendstrue ?string.SplitS,"." :[S,"0"] typeAddHelperSplitToArr S1extendsAdvancedNumericCharacters, S2extendsAdvancedNumericCharacters, Result=[SplitByPoint,SplitByPoint] =Resultextends[[`${number}`,`${number}`],[`${number}`,`${number}`]] ?Result :never typeAddFillZeroHelper Dataextends[[`${number}`,`${number}`],[`${number}`,`${number}`]], Result=[ [ string.PadStartData[0][0],string.GetStringLengthData[1][0],"0", string.PadEndData[0][1],string.GetStringLengthData[1][1],"0" ], [ string.PadStartData[1][0],string.GetStringLengthData[0][0],"0", string.PadEndData[1][1],string.GetStringLengthData[0][1],"0" ] ] =Resultextends[[`${number}`,`${number}`],[`${number}`,`${number}`]] ?Result :never typeAddReverseData Dataextends[[`${number}`,`${number}`],[`${number}`,`${number}`]], Result=[ [ array.Reversestring.SplitData[0][0], array.Reversestring.SplitData[0][1] ], [ array.Reversestring.SplitData[1][0], array.Reversestring.SplitData[1][1] ] ] =Resultextends[ [`${Numbers}`[],`${Numbers}`[]], [`${Numbers}`[],`${Numbers}`[]] ] ?Result :never typeStepAdderHelper DataLeftextends`${Numbers}`[], DataRightextends`${Numbers}`[], Curryextends`${Numbers}`=`${0}`, Offsetextendsnumber=0, ResultCacheextends`${number}`[]=[], NextOffsetextendsnumber=number.IntAddSingleOffset,1, CurrentextendsAddMap[Numbers][Numbers]=AddMap[DataLeft[Offset]][DataRight[Offset]], CurrentWidthPreCurryextends`${Numbers}`=AddMap[Current["result"]][Curry]["result"] =DataLeft["length"]extendsDataRight["length"] ?`${Offset}`extends`${DataLeft["length"]}` ?ResultCache :StepAdderHelper DataLeft, DataRight, Current["add"], NextOffset, common.And number.IsEqualCurrent["add"],"1", number.IsEqual`${NextOffset}`,`${DataLeft["length"]}` extendstrue ?array.Push["10",...ResultCache],CurrentWidthPreCurry :array.PushResultCache,CurrentWidthPreCurry :never typeNumbersWidthCurry=Numbers|10 typeMergeResultHelper Dataextends[ [`${Numbers}`[],`${Numbers}`[]], [`${Numbers}`[],`${Numbers}`[]] ], LeftIntextends`${Numbers}`[]=Data[0][0], LeftFloatextends`${Numbers}`[]=Data[0][1], RightIntextends`${Numbers}`[]=Data[1][0], RightFloatextends`${Numbers}`[]=Data[1][1], FloatAddedextends`${NumbersWidthCurry}`[]=StepAdderHelper LeftFloat, RightFloat , FloatHasCurryextendsboolean=FloatAdded[0]extends"10"?true:false, DeleteCurryFloatResultextendsunknown[]=FloatHasCurryextendstrue ?array.ShiftFloatAdded :FloatAdded, IntAddedextends`${NumbersWidthCurry}`[]=StepAdderHelper LeftInt, RightInt, FloatHasCurryextendstrue?`1`:"0" , IntHasCurryextendsboolean=IntAdded[0]extends"10"?true:false, DeleteCurryIntResultextendsunknown[]=IntHasCurryextendstrue ?array.ShiftIntAdded :IntAdded, ResultReversed=array.Reverse LeftFloat["length"]extends0 ?DeleteCurryIntResult :array.Concat [...DeleteCurryFloatResult,"."], [...DeleteCurryIntResult] , FloatResult=array.Join ResultReversedextendsstring[] ?IntHasCurryextendstrue ?["1",...ResultReversed] :ResultReversed :never, "" =FloatResult typeAdd S1extendsAdvancedNumericCharacters, S2extendsAdvancedNumericCharacters =MergeResultHelper AddReverseDataAddFillZeroHelperAddHelperSplitToArrS1,S2 typeadd=Add"9007199254740991","9007199254740991" 相关文章推荐 本文原文 掘金:TypeScript 类型体操姿势合集通关总结--刷完 掘金:Ts 高手篇:22 个示例深入讲解 Ts 最晦涩难懂的高级类型工具 掘金:来做操吧!深入 TypeScript 高级类型和类型体操 掘金:模式匹配-让你 ts 类型体操水平暴增的套路 阅读原文

上一篇:2023-10-31_为什么城市需要电竞产业? 下一篇:2024-12-10_大模型「标王」硬气:不做Sora ,要帮更多企业做出Sora

TAG标签:

15
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价