Infer关键字用于条件中的类型推断。Typescript官网也用ReturnType的经典例子来说明它的作用:typeReturnType=Textends(...args:any[])=>inferR?R:任何;理解为:如果T继承了extends(...args:any[])=>any类型,则返回类型R,否则返回any。R是什么?extends(...args:any[])=>inferR中定义了R,即根据传入的参数类型推导出R。精读,我们可以从两个角度理解推断,即需求角度和设计角度。从需求的角度理解infer的实现关键字infer背后一定是有需求的,这是普通的Typescript能力无法满足的。想象这样一个场景:实现一个接收数组并返回第一项的函数。我们不能用泛型来描述这种类型的推导,因为泛型类型是一个整体,我们要返回的是其中一个入参,不能像T[0]这样写得到第一项Type:functionxxx(...args:T[]):T[0]不支持这种写法也是合理的,因为这次是获取第一个item类型,如果T是一个对象,我们要返回KeyonChange的返回值类型,不知道怎么写。所以这个时候就必须要用到一个新的语法,就是infer。从设计的角度理解infer从类型推导的角度来看,泛型是非常强大的。我们可以使用泛型来描述调用时传入的类型,提前在类型表达式中描述:functionxxx(value:T):{result:T}但是我们发现泛型类型T是太全面了,我们没有能力从中挑选子类型。即对于xxx<{label:string}>的场景,T={label:string},但是我们不能将R定义为{label:R},因为泛型是一个不可分割的整体。事实上,为了类型安全,我们不能允许用户描述任意类型位置。如果传入的类型结构不是{label:xxx}而是一个callback()=>void,是不是基于错误环境的子类型推导。所以,考虑到要得到{label:inferR},参数首先要有{label:xxx}这样的结构,那么可以直接把infer和条件判断Textends结合起来?A:B,即:typeGetLabelTypeFromObject=Textends?{标签:推断R}?R:nevertypeResult=GetLabelTypeFromObject<{label:string}>;//typeResult=string也就是说,如果T遵循这样的结构如{label:any},那么我可以把这个结构中的任意变量位置替换??为推断xxx。如果输入的类型满足这个结构(通过TS静态分析判断),那么可以根据这个结构继续推导,所以我们在推导过程中可以使用inferxxx推导的变量类型。回顾第一个需求,可以使用infer来实现第一个参数类型:typeGetFirstParamType=Textends?(...args:inferR)=>任何?R[0]:never可以理解为,如果此时T满足结构体(...args:any)=>any,我们用inferR表示临时变量R指的是第一个any运行时类型,那么整个函数返回的类型就是R。如果T不满足(...args:any)=>任何结构,比如GetFirstParamType,那么这种推导无从谈起,never类型直接返回。当然,你也可以自定义如anyanytype。概述理解了infer的含义后,我们再结合conditionalinfer来理解本文的例子,有助于加深记忆。输入ArrayElementType=Textends(inferE)[]?E:T;//item1的类型是`number`typeitem1=ArrayElementType;//item1的类型是`{name:string}`typeitem2=ArrayElementType<{name:string}>;可以看出ArrayElementType使用条件推理和推断来表达这样一个逻辑:如果类型T是一个数组,我们将数组的每一项定义为类型E,那么返回类型是E,否则T是整体类型本身.所以对于item1,它满足结构,所以它返回number,但是item2不满足结构,所以它返回它自己的类型。特别是,对于以下示例应该返回什么?类型item3=ArrayElementType<[number,string]>;答案是数字|string,原因是我们使用多个inferE((inferE)[]等价于[inferE,inferE]...不只是多个变量指向同一个类型代词E)同时收到了number和string,所以可以这样理解,E有时是数字,有时是字符串,所以是or关系,也就是协变。如果是函数参数呢?类型Bar=Textends{a:(x:inferU)=>void;b:(x:推断U)=>void}?U:nevertypeT21=Bar<{a:(x:string)=>void;b:(x:数字)=>void}>;//string&number查找结果为string&number,即逆变。但在这个例子中,同一个U有时是一个字符串,有时是一个数字。为什么是and关系而不是or关系?事实上,协变或逆变与推断参数的位置有关。在TypeScript中,对象、类、数组、函数的返回值类型都是协变的,而函数的参数类型是逆变的,所以如果infer位置在函数参数上,就会遵循逆变原则。Inversionandcovariance:协方差(co-variant):类型收敛。逆变(contra-variant):类型发散。关于inversion和co-change的深入话题我可以另开一篇,这里就不赘述了,在这里理解infer就够了。总结infer关键字使我们能够深入扩展泛型结构,并在其中的任何地方挑选出类型,并将其用作最终返回类型的临时变量。对于Typescript类型的编程,最大的问题就是想要实现一个效果却不知道用什么语法。infer作为一个强大的类型推导关键字,在大多数复杂的类型推导场景中势必会派上用场,所以当你遇到困难时,可以考虑一下是否可以使用infer来解决问题。讨论地址为:Jingdu《Typescript infer 关键字》·Issue#346·dt-fe/weekly想参与讨论的请戳这里,每周都有新话题,周末或周一发布。前端精读——帮你过滤靠谱的内容。关注前端精读微信公众号版权声明:免费转载-非商业-非衍生保留属性(CreativeCommons3.0License)