前言
以前一直不会用infer,要么直接就是returnType,压根不需要用infer,网上那些教程只给示例不给具体场景就无法让人很好理解这玩意。
类型分发
对于infer,最好应该先说一下类型分发,虽然这2关系不是太大,但是如果把infer与类型分发结合起来,让人一看就觉得这人ts水平可以。至于协变与逆变等概念会比较容易让人搞混乱,可以以后再掌握。 我以前也学过这个,但是并不是能完全掌握它的使用时机,也不知道如何用,所以看别人用能看懂和自己能用完全是2种状态。 首先看一下类型分发的基本例子:
interface Fish {
fish: string
}
interface Water {
water: string
}
interface Bird {
bird: string
}
interface Sky {
sky: string
}
type Condition< T > = T extends Fish ? Water : Sky;
let condition1: Condition< Fish | Bird> = { water: '水' } ;
let condition2: Condition< Fish | Bird> = { sky: '天空' } ;
相信这个例子大家很容易理解,但是实际中什么时候用,怎么用,完全不知道。 这个例子有个特点,就是下面的condition1和condition2里定义的类型里所传的泛型与后面赋值的类型并不一样。 也就是说,类型分发一般是用来先知道已知类型,赋的值的类型会基于这个分发进行判断推出相应类型。 乍看之下好像还是没什么卵用,比如condition1,我都知道类型,我直接写个Sky|Water类型不香?干嘛二货的还搞个类型分发? 上面那个例子确实没啥卵用,但是如果判断继承的也是泛型,那么就可以快速取出一些类型,而不用自己重新去定义:(虽然这些很多都是内置的)
type Diff< T , U > = T extends U ? never : T ;
type R = Diff< "a" | "b" | "c" | "d" , "a" | "c" | "f" > ;
type Filter< T , U > = T extends U ? T : never;
type R1 = Filter< string | number | boolean , number > ;
既然有内置的,还不是没卵用。。。所以这就需要和infer联合使用才能看出牛b之处。
infer
infer大家应该都知道,returnType就是infer搞得,代码是这样:
type ReturnType< T extends ( ... args: any [ ] ) => any > = T extends ( ... args: any [ ] ) => infer R ? R : any ;
乍看之下好像有点难懂,其实仔细看发现还是很好理解的,它也是个类型分发。 学到这里,很多人可能就只知道有这个东西,但是什么时候用Infer完全不知道,我也是这样,后来再次听课时突发灵感,发现这个infer其实就相当于占位,也就是一个不知道的类型,用infer X去给他占位,再结合类型分发,就能玩出花样来了。当时还有小伙伴这么问:ts不是能自动推断类型吗?为什么需要Infer X去推断类型。卧槽,这个问的太好了,这个就是理解Infer的关键。 我们先结合个示例来进行说明:
export { }
type Parameters< T > = T extends ( ... args: infer R ) => any ? R : any ;
type T0 = Parameters< ( ) => string > ;
type T1 = Parameters< ( s: string ) => void > ;
type T2 = Parameters< ( < T > ( arg: T ) => T ) > ;
这个parameter也是内置的,可以看见,也是个类型分发,跟returnType区别就是infer X的占位跑到参数上去定义类型了。 如果我们把infer R换成已知类型,那么这个类型分发就跟一开始的demo没太大区别:
type Parameters< T > = T extends ( ... args: string [ ] ) => any ? string [ ] : any ;
type T0 = Parameters< ( ) => string > ;
如果不换成已知类型,那么只写R不写infer会报错,因为不知道R是什么东西。 那么如果通过泛型传呢?可惜args必须是个数组类型,所以用泛型传还得限定下它的条件:
type Parameters< T , R extends Array < any >> = T extends ( ... args: R ) => any ? R : any ;
type T0 = Parameters< ( ) => string , string [ ] > ;
可以发现,这么传跟已知类型传其实没太大区别,因为在传第二个泛型的时候,这个类型我们是知道的,所以这种情况,也没什么太大用处,除非传泛型的是另一个人,那么我们在写这个库的时候,倒是可以拿到用户所定义的类型。这时倒是有点作用。 这样一换就可以发现,infer可以在类型推导中去占任何位置,最后的推导的类型可以借助这之间所需的类型。可以看下这个例子加深理解:
type T1 = { name: string } ;
type T2 = { age: number } ;
type UnionToIntersection< T > = T extends { a: ( x: infer U ) => void ; b: ( x: infer U ) => void } ? U : never;
type T3 = UnionToIntersection< { a: ( x: T1 ) => void ; b: ( x: T2 ) => void } > ;
interface Action < T > {
payload? : T ;
type : string ;
}
class EffectModule {
count = 1 ;
message = "hello!" ;
delay ( input: Promise< number > ) {
return input. then ( i => ( {
payload: `hello ${ i} !` ,
type : 'delay'
} ) ) ;
}
setMessage ( action: Action< Date> ) {
return {
payload: action. payload! . getMilliseconds ( ) ,
type : "set-message"
} ;
}
}
type Connect = ( module : EffectModule) => any ;
const connect: Connect = m => ( {
delay: ( input: number ) => ( {
type : 'delay' ,
payload: `hello 2`
} ) ,
setMessage: ( input: Date) => ( {
type : "set-message" ,
payload: input. getMilliseconds ( )
} )
} ) ;
type Connected = {
delay ( input: number ) : Action< string > ;
setMessage ( action: Date) : Action< number > ;
} ;
export const connected: Connected = connect ( new EffectModule ( ) ) ;
要求修改那个any,使其返回正确类型,而且这个类型要和connected一样。 有同学说,直接把any改成connected不就完了?要是这么简单也不会出这题。这个肯定是要你推出来,并且这个connected它的类型是EffectModule实例上的方法,里面的参数与返回还修改了。 这题怎么做呢,先一步步来,先提取出effectModule的方法,不然没法下一步。 提取class方法没有现成的,肯定不能keyof EffectModule,因为还有别的东西,怎么排除别的玩意呢?就是利用类型分发和class可以取值来做,如果是函数,那就提取,否则就不提取:
type MethodName< T > = { [ F in keyof T ] : T [ F ] extends Function ? F : never} [ keyof T ]
type EE = MethodName< EffectModule>
这里同时利用value如果是never 则keyof就不会返回。这段其实挺有启发性,因为很多时候,都想搞个循环判断类型,然后进行选择,这就是个很好的范例。 拿到了name然后要改装方法它需要:
asyncMethod< T , U > ( input: Promise< T > ) : Promise< Action< U >> 变成了
asyncMethod< T , U > ( input: T ) : Action< U >
syncMethod< T , U > ( action: Action< T > ) : Action< U > 变成了
syncMethod< T , U > ( action: T ) : Action< U >
type asyncMethod< T , U > = ( input: Promise< T > ) => Promise< Action< U >>
type asyncMethodConnect< T , U > = ( input: T ) => Action< U >
type syncMethod< T , U > = ( action: Action< T > ) => Action< U >
type syncMethodConnect< T , U > = ( action: T ) => Action< U >
然后需要做一个类型分发,用来判断是哪个方法,再分发给哪个方法:
type EffectMethodAssign< T > = T extends asyncMethod < infer U , infer V > ?
asyncMethodConnect< U , V >
: T extends syncMethod < infer U , infer V >
? syncMethodConnect< U , V >
: never
这段很简单,就是分发判断,泛型是用infer占位ok。 最后,修改connect,就大功告成
type Connect = ( module : EffectModule) => {
[ F in MethodName< typeof module > ] : EffectMethodAssign< typeof module [ F ] >
} ;