1.背景:
infer关键字的出现,是在这个PR:https://github.com/Microsoft/TypeScript/pull/21496
原文:
This PR introduces the ability to use type inference in conditional types (#21316), enabling pattern matching for types. For example, the following extracts the return type of a function type:
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : T;
它的使用方式,一般是先判断某个泛型是否符合提取的条件,如果满足,则通过infer关键字去进行提取。示例代码,是提取函数参数的情况。
使用的套路一般是这样的:
接收泛型参数(T)
判断泛型参数是否符合某个类型(使用extends),如果符合,提取类型R(使用infer)
type InferWays<T> = T extends infer R ? R : never;
2.使用:
2.1 提取函数的参数类型
思路:我们创建一个FnArgType类型,接收泛型,判断泛型是否符合匹配的类型(使用infer 去提取函数入参类型),如果是,返回infer的类型,否则返回never;
const add = (a:number,b:number) => a+b
type FnArgType<T> = T extends (...args:infer Args) => any ? Args : never;
type FnArgTypeResult = FnArgType<typeof add>
// type FnArgTypeResult = [a: number, b: number]
2.2 提取函数的返回类型
和FnArgType类似,只是infer提取的部分在返参。
const add = (a: number, b: number) => a + b;
type FnReturnType<T> = T extends (...args:any[]) => infer R ? R : never;
type FnReturnTypeResult = FnReturnType<typeof add>
2.3 提取首尾字符串
要点是将需要判断的字符串类型通过`${infer First}${infer Last}`
的形式提取出来
const str = "abcde";
type FirstLetter<Str extends string> =
Str extends `${infer First}${infer Rest}` ? First : never;
type LastLetter<Str extends string> =
Str extends `${infer First}${infer Rest}` ? Rest : never;
type FirstLetterTest = FirstLetter<typeof str>;
// type FirstLetterTest = "a"
type LastLetterTest = LastLetter<typeof str>;
// type LastLetterTest = LastLetter<typeof str>;
2.4 提取字符串类型,拆分成联合类型
示例:
const str = "abc";
type SplitToUnionResult = SplitToUnion<typeof str>
// 等同于 type SplitToUnionResult = "a" | "b" | "c"
实现:
长度不确定,可以通过递归的方式去解决
我们可以接受两个泛型,第一个泛型是需要处理的字符串,第二个泛型是一个累加器
累加器处理的逻辑:当前字符串的类型,和累加器的字符串类型并为联合类型
每次处理一个字符串,将剩余的字符串放入递归中处理
递归结束时,返回累加器处理结果
const str = "abcde";
type SplitToUnion<
Str extends string,
Total = never
> = Str extends `${infer First}${infer Rest}`
? First extends string
? SplitToUnion<Rest, First | Total>
: never
: Total;
type R = SplitToUnion<typeof str>;
2.5 提取数组头尾
其实和字符串提取头尾的思路是相似的,对应的格式应该是 [infer First, ...infer Rest]
type FirstItem<T extends any[]> = T extends [infer First, ...infer Rest]
? First
: never;
type LastItem<T extends any[]> = T extends [...infer Rest, infer Last]
? Last
: never;
type Example = ["hello", "world"];
type FirstItemResult = FirstItem<Example>;
// type FirstItemResult = "hello"
type LastItemResult = LastItem<Example>;
// type LastItemResult = "world"
2.6 提取数组头尾+构造
比如我想实现一个ArrayZip类型,接受两个泛型,约束泛型的类型必须是数组
然后将数字的每两项依次按长度进行组合,组合成一个数组,
最终返回一个二维数组的类型
type ArrayZipResult = ArrayZip<[1, 2, 3], [4, 5]>;
// 等同于 type ArrayZipResult = [[1,4],[2,5]]
type ArrayZip<First extends any[], Second extends any[]> = First extends [
infer FirstHead,
...infer FirstRest
]
? Second extends [infer SecondHead, ...infer SecondRest]
? [[FirstHead, SecondHead], ...ArrayZip<FirstRest, SecondRest>]
: []
: [];
// 等同于 type ArrayZipResult = [[1,4],[2,5]]
解决的思路:infer需要提取两个泛型的首个元素,如果均能提取,组合成一个二维数组,并将剩余的元素再使用ArrayZip进行递归操作。
如果无法提取,证明已经处理完毕,返回空数组。
2.6 提取 Promise的返回值
实现一个 PromiseValue类型,用于提取Promise类型的返回类型
type PromiseValue<T> = T extends Promise<infer V> ? V : never;
const service: Promise<number> = new Promise((resolve) => resolve(10));
type PromiseValueTest = PromiseValue<typeof service>;
// type PromiseValueTest = number
严谨一些,我们还可以进行递归处理,比如PromiseValue提取到的如果还是一个Promise类型,再继续做处理
type PromiseValueDeep<T> = T extends Promise<infer U>
? U extends Promise<any>
? PromiseValueDeep<U>
: U
: never;
type PromiseValueDeepResult = PromiseValueDeep<Promise<Promise<Promise<any>>>>;
// type PromiseValueDeepResult = any
2.7 提取构造函数 Constructor 参数
其实和函数提取参数的方式是类似的,唯一区别在于构造函数的类型是:
T extends new (...args: infer Args) => unknown
interface UserInfo {
name: string;
age: number;
}
class User {
userInfo: UserInfo;
constructor(userInfo: UserInfo) {
this.userInfo = userInfo;
}
get() {
return this.userInfo;
}
}
type ConstructorArgs<T> = T extends new (...args: infer Args) => unknown
? Args
: never;
type ConstructorArgsTest = ConstructorArgs<typeof User>;
3.infer 的使用总结
infer的使用:
需要通过 extends 做类型条件判断,通过infer去提取,提取之后可以用于构造新的类型
如果需要构造的新类型长度不确定,可以通过递归+累加的方式去做实现