持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情
前言
本篇文章将会介绍一下 TS 中的 infer 关键字的作用,以及它会用来解决实际开发中的什么问题,infer 的出现是为了解决什么,并且在文章的最后,会有一道综合的题目,这道题的难度可能会比较大,在之后的文章当中会继续做出详细的讲解。
什么是 infer 关键字
infer 用于声明类型变量,以存储在模式匹配过程中所捕获的类型。
这里的模式匹配指的是条件类型的执行过程
type UnpackedArray<T> = T extends (infer U)[] ? U : T
type U0 = UnpackedArray<string[]> // string
语法
T extends (infer U)[] ? U : T
注意点
需要注意的是,infer 只能在条件类型的 extends 子句中使用,同时 infer 声明的类型变量只在条件类型的 true 分支中可用。
infer 关键字有什么用,用来解决什么问题?
比如说在我们实际开发的过程当中,存在一个用户列表,这个列表是一个对象数组类型:
type Users = {
name:string;
age:number
}[]
那么在某些情况下需要获取到其中的一个用户对象,那么在不知道 infer 关键字 之前,就只能够再去定义一个新的类型:
type User = {
name: string;
age: number;
}
这样很明显两个类型有大部分的重复,但是我们可以通过 infer 关键字 来获取到数组类型中的元素,定义一个通用的泛型,之后都可以用于简便的获取数组中的元素类型
type Users = {
name:string;
age:number
}[]
type UnpackedArray<T> = T extends (infer R)[] ? R : never
type User = UnpackedArray<Users>
// type User = {
// name: string;
// age: number;
// }
当然不仅仅只是数组,还可以获取函数中的入参,或者返回值类型:
type Fn = (a:number)=>string
type UnpackedFn1<T> = T extends (...args: any[]) => infer R ? R : never;
type UnpackedFn2<T> = T extends (args: infer R) => void ? R : never;
type T1 = UnpackedFn1<Fn>
// type T1 = string
type T2 = UnpackedFn2<Fn>
// type T2 = number
结合条件链
上面我们说到了 infer 关键字 只能在 true 分支中使用,那么有什么办法能够判断多种不同的情况呢,答案就是使用我们之前讲到过的条件链,多个条件类型组合就会变成条件链,有点类似于 JS 中的 if ... else if ... 语法。
比方说现在我们要用一个泛型来判断一个函数是数组还是函数,数组的话就获取数组中的元素类型,函数的话就获取它的返回值类型:
type Unpacked<T> = T extends (infer R)[]
? R :
T extends (args: any) => infer R
? R : never;
type A = Unpacked<number[]>
// type A = number
type B = Unpacked<()=>number>
// type B = number
根据继续叠加条件链,我们还能够用这个泛型来获取更多的类型,比方说 promise 中的返回值类型,对象中某个key对应的值的类型,等等都是可以的。
关于定义多个 infer
上面的例子当中,我们都只定义了一个 infer 关键字,但是如果定义多个infer关键字,并且定义的变量是一样的,那么会是什么样的结果呢?
type User = {
name:string;
age:number
}
type A<T> = T extends {name: infer R,age: infer R} ? R : never
type A1 = A<User>
// type A1 = string | number
上面这个例子,我们会发现,多个infer 类型推断出来的类型竟然是多个类型的联合类型,那么是不是所有情况都是联合类型呢,答案当然不是的,比方说:
type User = {
name:(A:{a:1})=>void;
age:(B:{b:1})=>void
}
type A<T> = T extends
{
name:(a: infer R)=>void,
age: (b: infer R)=>void,
}
? R : never
type A1 = A<User>
// type A1 = {
// a: 1;
// } & {
// b: 1;
// }
对于函数的入参来说,定义多个infer又变成了交叉类型,那么这其中有什么规律呢,答案就是逆变和协变,在协变位上,同一个类型的多个候选者,会被推断为联合类型,逆变位置上同一个类型的多个候选者,会被推断为交叉类型。
不懂什么是逆变协变的小伙伴可以先阅读一下之前的介绍逆变协变的文章
综合应用题-联合类型 to 交叉类型
文章最后放一道题目,将联合类型转换为交叉类型:
type UnionToIntersection<T> = (
T extends any? (arg:T)=>void : never
) extends (arg:infer R)=>void
? R
:never
type S = UnionToIntersection<{a:1}|{b:2}>
// type S = {
// a: 1;
// } & {
// b: 2;
// }
这个工具类型的知识点都是有介绍过的了,在之后也会找机会来详细说说它的执行逻辑。
总结
本文简单的介绍了infer 关键字,并且介绍了 infer 关键字 在实际使用中能够用来做什么,infer 关键字的使用时离不开条件类型的,所以在这之前一定要先学会使用条件类型,并且要先搞懂逆变和协变,才能够深入的去了解 infer 关键字。