说明:目前网上还没有最新的TypeScript官方文档的中文翻译,所以才有了这样的翻译计划。因为本人也是TypeScript的初学者,不能保证翻译100%准确。如有错误,请在评论区指出;翻译内容:暂定翻译内容为TypeScriptHandbook,其他部分翻译文档稍后补充;项目地址:TypeScript-Doc-Zh,如果对你有帮助,可以点个star~本章官方文档地址:ConditionalTypesConditionaltypes在大多数应用的核心,我们需要根据哪些逻辑来决定执行在输入上。JavaScript应用程序也是如此,但是由于值很容易内省(译者注:内省是指代码能够检查自身,访问内部属性,获取代码的低级信息),那么哪些逻辑执行取决于输入数据的类型。条件类型可以用来描述输入类型和输出类型之间的关系。interfaceAnimal{live():void;}interfaceDogextendsAnimal{woof():void;}typeExample1=DogextendsAnimal?数字:字符串;^//typeExample1=numbertypeExample2=RegExpextendsAnimal?数字:字符串;^//typeExample2=string条件类型的形式有点像JavaScript中的条件表达式(条件?真分支:假分支):SomeTypeextendsOtherType?TrueType:假类型;当extends左边的类型可以赋值给右边的类型时,最后的结果是第一个分支中的类型(真分支),否则是第二个分支中的类型(假分支)。仅仅从上面的例子来看,条件类型似乎不是很有用——即使我们不依赖它,我们也可以知道DogextendsAnimal是否为真,然后选择相应的数字类型或字符串类型!但是,当条件类型与泛型结合使用时,它们会发挥巨大的作用。例如,让我们看一下下面的createLabel函数:(名称:字符串):NameLabel;函数createLabel(nameOrId:字符串|编号):IdLabel|NameLabel;函数createLabel(nameOrId:string|number):IdLabel|NameLabel{throw"unimplemented";}createLabel函数使用了重载,根据不同的输入类型选择不同的输出类型。请注意,这有一些问题:如果一个库必须在整个API中一遍又一遍地做出相同的选择,那么这会变得很麻烦。我们需要创建三个重载:前两个用于特定输入类型(字符串和数字),最后一个用于最一般的情况(输入类型字符串|数字)。一旦createLabel添加了它可以处理的新类型,重载的数量就会呈指数级增长。那么我们换一种说法,我们可以将上面代码的逻辑编码成一个条件类型:typeNameOrId=Textendsnumber?IdLabel:名称标签;然后,我们可以使用这个条件类型将原来的重载函数缩减为一个没有重载的函数:functioncreateLabel(idOrName:T):NameOrId{throw"unimplemented";}leta=createLabel("typescript");^//leta:NameLabelletb=createLabel(2.8);^//让b:IdLabel让c=createLabel(Math.random()?"hello":42);^//让c:NameLabel|IdLabel条件类型约束通常,条件类型中的检查会为我们提供一些新信息。正如使用类型保护的类型缩小可以产生更具体的类型一样,条件类型的真实分支可以通过我们检查的类型进一步限制泛型类型。以下面的代码为例:typeMessageOf=T['message'];//类型'"message"'不能用于索引类型'T'。在此代码中,TypeScript抛出一个错误,因为它无法确定T是否具有message属性。我们可以约束T,以便TypeScript不再报告错误:^//typeEmailMessageContents=string然而,当message属性不存在时,我们想让MessageOf接受任何类型,默认为never,那该怎么办呢?我们可以移除约束并引入条件类型:typeMessageOf=Textends{message:unknown}?T['message']:neverinterfaceEmail{message:string;}interfaceDog{bark():void;}typeEmialMessageContents=MessageOf;^//类型EmialMessageContents=string类型DogMessageContents=MessageOf;^//typeDogMessageContents=never在条件类型的true分支中,TypeScript知道T将具有消息属性。让我们看另一个例子。我们可以写一个Flatten函数,它可以将数组类型扁平化为数组中元素的类型,并为非数组类型保留其原始类型:typeFlatten=Textendsany[]?T[数字]:T;//提取元素类型typeStr=Flatten;^//typeStr=string//保留原始类型typeNum=Flatten;^//typeNum=number当Flatten接受数组类型时,会使用number通过索引访问来提取数组typestring[]中的元素类型;如果它接受非数组类型,它将直接返回给定的原始类型。条件类型中的推断在上面的示例中,我们使用条件类型来应用约束和提取类型。由于此操作非常常见,因此条件类型提供了一种更简单的方法来执行此操作。条件类型提供了infer关键字,可以让我们在条件中推断出某种类型,并应用到真正的分支上。例如,在上面的Flatten函数中,我们可以直接推断数组元素的类型,而不是通过索引访问“手动”提取元素类型:typeFlatten=TypeextendsArray?物品种类;在这里,我们使用infer关键字声明性地引入一个新的泛型类型变量Item,而不是指定如何在true分支中提取T数组的元素类型。这使我们不必考虑如何弄清楚我们感兴趣的类型的结构。我们可以使用infer关键字编写一些有用的实用类型别名。例如,在一些简单的情况下,我们可以从函数类型中提取返回类型:typeGetReturnType=Typeextends(...args:never[])=>inferReturn?Return:never;typeNum=GetReturnType<()=>number>;^//typeNum=numbertypeStr=GetReturnType<(x:string)=>string>;^//类型Str=stringtypeBools=GetReturnType<(a:boolean,b:boolean)=>boolean[]>;^//typeBools=boolean[]如果对具有多个调用签名的类型(例如重载函数的类型)进行推断,则只会对最后一个签名(即最一般的情况)进行推断).声明函数stringOrNum(x:string):number;declarefunctionstringOrNum(x:number):string;declarefunctionstringOrNum(x:string|number):string|数字;typeT1=ReturnType;^//输入T1=字符串|number可分配条件类型当条件类型作用于泛型时,如果给定一个联合类型,那么此时的条件类型是可分配的。例如,看下面的代码:typeToArray=Typeextendsany?类型[]:从不;如果我们将联合类型传递给toArray,则条件类型将应用于联合类型的每个成员。typeToArray=Typeextendsany?Type[]:从不;类型StrArrOrNumArr=ToArray;^//输入StrArrOrNumArr=string[]|number[]StrArrOrNumArr赋值出现在以下联合类型中:string|number然后有效地将联合类型的每个成员映射到一个数组,如下所示:ToArray|ToArray<数字>;最后得到一个这样的数组:string[]|数字[];通常,这是我们所期望的行为。如果要规避这种行为,可以将extends关键字用左右两边的方括号括起来。输入ToArrayNonDist=[Type]extends[any]?Type[]:never;//'StrArrOrNumArr'不再是联合类型typeStrArrOrNumArr=ToArrayNonDist;^//输入StrArrOrNumArr=(string|number)[]