当前位置: 首页 > Web前端 > HTML

精读《Diff, AnyOf, IsUnion...》

时间:2023-03-28 15:37:55 HTML

解决TS题最好的方法就是多练习。这次的interpretingtype-challengesMedium的难度是25~32题。IntensiveDiff实现了Diff,返回一个新对象,类型为两个对象类型的Diff:typeFoo={name:stringage:string}typeBar={name:stringage:stringgender:number}equal//{gender:number}首先我们要思考Diff的计算方式。A和B的Diff就是求A存在B不存在,B存在A不存在的值。然后Exclude函数,它可以得到X中存在Y中不存在的值,我们只需要将X和Y分别替换为keyofA,keyofB,交替A和B的位置就可以得到Diff://这个问题的答案类型Diff={[KinExclude|Exclude]:KextendskeyofA?A[K]:(KextendskeyofB?B[K]:never)}Value我们之前提到的小技巧,就是需要使用两组三元运算符来保证访问的下标存在于对象中,也就是extendskeyof的语法技巧。AnyOf实现AnyOf函数,如果任何一项为真则返回真,否则返回假,对于空数组返回假:typeSample1=AnyOf<[1,'',false,[],{}]>//预期为true.typeSample2=AnyOf<[0,'',false,[],{}]>//预期为false。这道题有几个问题需要思考:第一,应该用什么样的判断方法?像这样判断数组中任意一个元素是否满足某个条件的问题就可以递归解决了。具体来说,先判断数组中的第一项,若满足则继续递归判断其余项,否则终止判断。这可以做到,但是很麻烦。还有一个技巧就是使用extendsArray<>方法让TS自动帮你遍历。二是如何判断某一项为真?有很多情况是正确的,我们尝试列举错误的情况:0undefined''undefinednullnever[]。结合以上两点考虑,不难回答这个问题如下:typeFalsy=''|从不|未定义|空|0|假|[]typeAnyOf=TextendsFalsy[]?false:true但是这个测试用例失败了:AnyOf<[0,'',false,[],{}]>如果此时在Falsy中加上{},你会发现除了这个case,其他的判断都是原因是{a:1}extends{}为真,因为{}不代表空对象,而是所有对象类型,所以我们需要换成Record来锁定空对象://回答这个问题类型Falsy=''|从不|未定义|空|0|假|[]|RecordtypeAnyOf=TextendsFalsy[]?false:trueIsNever实现IsNever来判断值类型是否为never:typeA=IsNever//应该是truetypeB=IsNever//应该是falsetypeC=IsNever//应该是falsetypeD=IsNever<[]>//expectedtobefalsetypeE=IsNever//expectedtobefalse首先我们可以毫不犹豫地写下错误答案:typeIsNever=Textendsnever?true:false这个错误答案一定是接近正确答案,错误是不能判断never。在Permutation全排列题中,我们体会到了never在泛型中的特殊性。不会触发extends判断,而是直接终止,使判断无效。而且解决方法也很简单,绕过never特性,打包一个数组即可://TheanswertothisquestiontypeIsNever=[T]extends[never]?true:falseIsUnionimplementIsUnion判断是否为联合类型:typecase1=IsUnion//falsetypecase2=IsUnion//truetypecase3=IsUnion<[string|number]>//false这个问题完全是脑筋急转弯,因为TS要知道传入的类型是不是联合类型,会对联合类型进行特殊处理,但是联合类型的判断语法没有暴露,所以我们只能测试传入type来推断它是否是联合类型。至此,我们能想到的联合类型只有两个特点:TS在将泛型作为联合类型处理时,进行了分布式处理,即将联合类型拆解成独立的项一一确定,最后使用|连接。用[]包装联合类型可以绕过分派功能。那么如何判断传入的泛型是联合类型呢?如果泛型派发,则可以推导出它是一个联合类型。难点转移到:如何判断泛型是分布式的?先分析下,分布的作用是什么:AextendsA//IfAis1|2、分配结果为:(1extends1|2)|(2extends1|2),即这个表达式会被执行两次,两次执行中第一次A的值分别为1和2,第二次A为1|2每次都在两次处决中,但两个表述都是真实的,不能体现特殊分配性。这时候我们就应该利用package[]的非分布特性,即分布后,由于每次执行过程中第一个A都是union类型的item,所以之后一定不等于原来的值用[]打包,所以我们在分发extends时用[]再次包裹extends。如果此时不匹配,则说明发生了分发:typeIsUnion=AextendsA?([A]extends[A]?false:true):false但是这段代码还是不正确,因为在第一个三元表达式括号中,A已经被分派了,所以[A]extends[A]即使是联合类型也为真,这时候需要把extends后面的[A]换成原来的值,出现show操作:typeIsUnion=AextendsA?([B]extends[A]?false:true):false虽然我们声明了B=A,但是因为A在过程中是分布式的,所以B在运行时不等于A,这就达到了我们的目的。[B]放在extends前面,因为B还没有被分发,不能包含在分发结果中,所以分发时这个条件一定为false。最后,由于测试用例有never条件,我们可以使用IsNever函数提前判断://本题答案typeIsUnion=IsNeverextendstrue?false:(AextendsA?([B]extends[A]?false:true):false)从这个问题中,我们可以深刻理解TS的诡异之处,即extends前面的TintypeX=Textends...不一定是你看到传入的T,如果是联合类型,就会作为单一类型分发,单独处理。ReplaceKeys实现ReplaceKeys将Obj中每个对象的KeysKey类型转换为与Targets对象对应的Key描述相匹配的类型。如果Targets无法匹配,则类型设置为never:typeNodeA={type:'A'name:stringflag:number}typeNodeB={type:'B'id:numberflag:number}typeNodeC={类型:'C'名称:字符串标志:数字}typeNodes=NodeA|节点B|NodeCtypeReplacedNodes=ReplaceKeys<节点,'名称'|'flag',{name:number,flag:string}>//{type:'A',name:number,flag:string}|{type:'B',id:number,flag:string}|{type:'C',name:number,flag:string}//会将名称从字符串替换为数字,将标志从数字替换为字符串。typeReplacedNotExistKeys=ReplaceKeys//{type:'A',name:never,flag:number}|节点B|{type:'C',name:never,flag:number}//wouldreplacenametonever这道题别看描述很吓人,其实很简单。思路:利用keyofObj中的K遍历原对象的所有Key。如果Key在描述的Keys中并且存在于Targets中,则返回Targets[K]类型,否则返回never,如果不存在则描述的Keys在对象的原始类中使用类型://对此问题的回答类型ReplaceKeys={[KinkeyofObj]:KextendsKeys?(KextendskeyofTargets?Targets[K]:never):Obj[K]}RemoveIndexSignatureImplementRemoveIndexSignature删除对象中的Index下标:typeFoo={[key:string]:any;foo():void;}typeA=RemoveIndexSignature//expected{foo():void}本题重点是如何识别对象字符串Key,可以使用\`${inferP}\`查看是否可以识别P来判断当前字符串Key是否被命中://TheanswertothisquestiontypeRemoveIndexSignature={[KinkeyofTasKextends`${inferP}`?P:never]:T[K]}PercentageParser实现PercentageParser,解析出百分比字符串的符号位和数字:typePString1=''typePString2='+85%'typePString3='-85%'typePString4='85%'typePString5='85'typeR1=PercentageParser//预期['','','']typeR2=PercentageParser//预期["+","85","%"]typeR3=PercentageParser//预期["-","85","%"]typeR4=PercentageParser//expected["","85","%"]typeR5=PercentageParser//expected["","85",""]这个问题完全解释了TS没有正则化能力,尽量不要做正则的事情^_^回到正题,如果非要用TS来实现,只能列举各种场景://本题答案typePercentageParser=//+/-xxx%Aextends`${inferXextends'+'|'-'}${inferY}%`?[X,Y,'%']:(//+/-xxxAextends`${inferXextends'+'|'-'}${inferY}`?[X,Y,'']:(//xxx%Aextends`${inferX}%`?['',X,'%']:(//xxx包括['100','%','']这三种情况Aextends`${inferX}`?['',X,'']:never)))这道题用到了infer可以进行无限分支判断的知识。DropChar实现DropChar以从字符串中删除指定的字符:typeButterfly=DropChar<'butterfly!',''>//'蝴蝶!'这道题和Replace很像,只是用递归不断排除C://AnswertothisquestiontypeDropChar=Sextends`${inferA}${C}${inferB}`?`${A}${DropChar}`:S总结写到这里,越来越觉得TS虽然有图灵完备性,但是在逻辑处理上不如JS方便。很多设计计算逻辑问题的解法都不是很优雅。但解决这类问题有助于加强对TS基本能力组合的理解和综合运用,在解决实际问题时不可或缺。讨论地址是:Jingdu《Diff, AnyOf, IsUnion...》·Issue#429·dt-fe/weekly想参与讨论的请戳这里,每周都有新话题,周末或周一发布。前端精读——帮你过滤靠谱的内容。关注前端精读微信公众号版权声明:免费转载-非商业-非衍生保留属性(CreativeCommons3.0License)