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

TypeScript前言的ConditionalTypes

时间:2023-04-05 23:42:07 HTML5

TypeScript的官方文档已经更新,但我能找到的中文文档还是旧版本。因此,对一些新增和修改的章节进行了翻译和整理。本文整理自TypeScript手册中的“条件类型”一章。本文并没有严格按照原文翻译,部分内容也做了说明和补充。条件类型(ConditionalTypes)很多时候,我们需要根据输入值来确定输出值,也需要根据输入值类型来确定输出值类型。条件类型用于帮助我们描述输入类型和输出类型之间的关系。interfaceAnimal{live():void;}interfaceDogextendsAnimal{woof():void;}typeExample1=DogextendsAnimal?数字:字符串;//typeExample1=numbertypeExample2=RegExpextendsAnimal?数字:字符串;//typeExample2=string条件类型的写法有点类似于JavaScript中的条件表达式(condition?trueExpression:falseExpression):SomeTypeextendsOtherType?TrueType:假类型;仅从这个例子中,可能无法看出条件类型有什么用。但是它在与泛型一起使用时很有用,让我们以下面的createLabel函数为例:}函数createLabel(id:数字):IdLabel;函数createLabel(名称:字符串):NameLabel;函数createLabel(nameOrId:字符串|数字):IdLabel|NameLabel;函数createLabel(nameOrId:string|number):IdLabel|NameLabel{throw"unimplemented";}这里使用函数重载来描述createLabel如何根据不同类型的输入值做出不同的决定,并返回不同的类型。当心这样的事情:如果一个库在遍历API后必须一遍又一遍地做出相同的选择,它会变得非常笨拙。我们必须创建三个重载,一个用于处理明确已知的类型,我们为每种类型编写一个(这里一个用于字符串,一个用于数字),一个用于一般情况(接收字符串|数字)。而且如果你添加一个新的类型,重载的数量会呈指数级增长。其实我们可以把逻辑写在conditiontype里面:typeNameOrId=Textendsnumber?IdLabel:名称标签;使用这种条件类型,我们可以简化函数重载:functioncreateLabel(idOrName:T):NameOrId{throw"unimplemented";}leta=createLabel("typescript");//leta:NameLabelletb=createLabel(2.8);//letb:IdLabelletc=createLabel(Math.random()?"hello":42);//让c:NameLabel|IdLabel条件类型约束(ConditionalTypeConstraints)通常,使用条件类型会给我们提供一些新的信息。正如类型保护可以用来缩小一个类型(narrowing)来为我们提供更具体的类型一样,条件类型的真正分支会进一步约束泛型类型,例如:typeMessageOf=T["message"];//类型'"message"'不能用于索引类型'T'。TypeScript报错是因为T不知道有一个名为message的属性。我们可以约束T以便TypeScript不再报告错误:typeMessageOf=T["message"];interfaceEmail{message:string;}typeEmailMessageContents=MessageOf;//typeEmailMessageContents=string但是如果我们想让MessgeOf传入任意类型,但是当传入的值没有message属性时,返回默认类型,例如从不?我们可以移出约束并使用条件类型:typeMessageOf=Textends{message:unknown}?T[“消息”]:从不;interfaceEmail{message:string;}interfaceDog{bark():void;}typeEmailMessageContents=MessageOf;//typeEmailMessageContents=stringtypeDogMessageContents=MessageOf;//typeDogMessageContents=never在真正的分支中,TypeScript会知道T有一个message属性。再举个例子,我们写一个Flatten类型来获取数组元素的类型。当输入不是数组时,直接返回输入类型:typeFlatten=Textendsany[]?T[数字]:T;//提取出元素type.typeStr=Flatten;//typeStr=string//单独保留类型。typeNum=Flatten;//typeNum=number这里注意indexaccesstype中的number索引是用来获取数组元素的类型的。InferringWithinConditionalTypes(InferringWithinConditionalTypes)条件类型提供了infer关键字,可以从被比较的类型中推断出类型,然后在true分支中引用推断出的结果。借助infer,我们修改Flatten的实现,不再使用索引访问类型“手动”获取:typeFlatten=TypeextendsArray?物品种类;这里我们使用infer关键字来声明一个新的类型变量Item,而不是像之前那样在true分支中显式写出如何获取T的元素类型,这样可以解放我们,让我们不用去思考如何获取T的元素类型从我们感兴趣的类型结构中挖掘出我们需要的类型结构。我们还可以使用infer关键字编写一些有用的辅助类型别名。例如,我们可以获得函数返回的类型:typeGetReturnType=Typeextends(...args:never[])=>inferReturn?返回:从不;typeNum=GetReturnType<()=>number>;//typeNum=numbertypeStr=GetReturnType<(x:string)=>string>;//typeStr=stringtypeBools=GetReturnType<(a:boolean,b:布尔值)=>布尔值[]>;//typeBools=boolean[]当从多个调用签名(如重载函数)中推断类型时,会根据最后一个签名推断,因为一般情况下都使用这个签名来处理。声明函数stringOrNum(x:string):number;declarefunctionstringOrNum(x:number):string;declarefunctionstringOrNum(x:string|number):string|数字;typeT1=ReturnType;//输入T1=字符串|number分布式条件类型(DistributiveConditionalTypes)在泛型中使用条件类型时,如果传入一个联合类型,就会变成分布式(distributive),例如:typeToArray=Typeextendsany?类型[]:从不;如果我们在ToArray中传入一个联合类型,这个条件类型将应用于联合类型的每个成员:typeToArray=Typeextendsany?类型[]:从不;输入StrArrOrNumArr=ToArray;//类型StrArrOrNumArr=string[]|number[]我们来分析一下StrArrOrNumArr发生了什么,这是我们传入的类型:string|数字;然后遍历联合类型Members,等价于:ToArray|ToArray<数字>;所以最后的结果是:string[]|数字[];通常这是我们所期望的行为,如果你想避免这种行为,你可以使用方括号包围extends关键字的每个部分的方法。输入ToArrayNonDist=[Type]extends[any]?类型[]:从不;//'StrArrOrNumArr'不再是一个union.typeStrArrOrNumArr=ToArrayNonDist;//typeStrArrOrNumArr=(string|number)[]TypeScript系列TypeScript系列文章由三部分组成:官方文档翻译、重点难点解析、实战技巧,涵盖入门、进阶、实战战斗。.点此浏览全系列文章,建议顺便收藏本站。微信:“mqyqingfeng”,加我到世优唯一的读者群。如有错误或不准确的地方,请务必指正,万分感谢。如果你喜欢或者有启发,欢迎star,这也是对作者的鼓励。