TypeScript infer 关键字
本文于487天之前发表,文中内容可能已经过时。
创建了一个 “重学TypeScript” 的微信群,想加群的小伙伴,加我微信 “semlinker”,备注 “1” 。阿里、京东、腾讯的大佬都在群里等你哟。
阅读须知:本文示例的运行环境是 TypeScript 官网的 Playground,对应的编译器版本是 v3.8.3。
一、类型提取
在 TypeScript 中我们能够很方便地从复合类型中提取出单个类型,以数组、元组或对象为例,我们可以通过成员访问的语法来提取数组、元组或对象中元素或属性的类型,具体示例如下:
1 | type Person = { |
对象访问语法同样也适用于接口,使用起来非常直观:
1 | interface Person { |
但是,更有趣的是,我们也可以从泛型和函数中提取类型。假设我们有以下的字典类型:
1 | interface Dictionary<T = any> { |
为了从 StrDict
类型中提取 T
类型,我们可以使用上面成员属性的方式:
1 | type StrDictMember = StrDict[""]; // string |
二、条件类型及 infer
其实除了使用以上的方式外,我们还有另一种选择,就是使用 TypeScript 中的 infer
关键字和条件类型:
1 | type DictMember<T> = T extends Dictionary<infer V> ? V : never |
在 TypeScript 2.8 中引入了条件类型,使得我们可以根据某些条件得到不同的类型,这里所说的条件是类型兼容性约束。尽管以上代码中使用了 extends
关键字,也不一定要强制满足继承关系,而是检查是否满足结构兼容性。
条件类型会以一个条件表达式进行类型关系检测,从而在两种类型中选择其一:
1 | T extends U ? X : Y |
以上表达式的意思是:若 T
能够赋值给 U
,那么类型是 X
,否则为 Y
。这很好理解,但在 T extends Dictionary<infer V> ? V : never
条件表达式中却多了一个 infer
关键字。在条件类型表达式中,我们可以用 infer
声明一个类型变量并且对它进行使用。
了解完条件类型和 infer
关键字,我们再来看一下完整的代码:
1 | interface Dictionary<T = any> { |
除了上述的应用外,利用条件类型和 infer
关键字,我们还可以方便地实现获取 Promise 对象的返回值类型,比如:
1 | async function stringPromise() { |
三、ReturnType
TypeScript 官方类型库中提供了 RetrunType
可获取方法的返回类型,其使用示例如下:
1 | type T0 = ReturnType<() => string>; // string |
为什么 ReturnType<string>
和 ReturnType<Function>
会抛出上述的异常呢?要解答这个问题,我们就需要来看一下 ReturnType
的定义:
1 | /** |
很明显 ReturnType
内部也是利用条件类型和 infer
关键字,来实现获取方法的返回类型。同理,我们也可以获取参数的类型:
1 | type Fn1 = (a: number) => string; |
如果你想要抽取函数中元组类型的所有参数的类型,这就变得更加有趣,在 TypeScript 3.0 版本之后,元组也支持剩余参数与展开参数,因此我们可以通过定义 ArgsType<T>
类型,来实现上述功能,具体代码如下:
1 | type VariadicFn<A extends any[]> = (...args: A) => any; |
infer
关键字除了上述介绍的应用场景之外,它还可以用于实现元组类型转联合类型、联合类型转交叉类型等,这里就不详细展开,大家如果有兴趣的话,可以阅读 深入理解 TypeScript - infer 章节的相关内容。
为了加深大家对 infer
关键字的理解,最后我们再来分析两个相对简单的示例。
示例一:
1 | type extractArrayType<T> = T extends (infer U)[] ? U : never; |
在上面代码中,我们使用泛型语法定义了一个名为 extractArrayType
的条件类型,该条件类型会判断是否类型 T 是属于数组类型,如果满足条件的话,我们使用 infer
关键字来声明一个新的类型变量 U 并返回该类型,否则返回 never 类型。这个例子相对比较简单,我们来看一个相对复杂的例子。
示例二:
1 | type InferredAb<T> = T extends { a: infer U, b: infer U } ? U : T; |
在上面代码中,我们使用泛型语法定义了一个名为 InferredAb
的条件类型,该条件类型会判断是否类型 T 是否包含 a 和 b 属性,如果满足条件的话,我们使用 infer
关键字来声明一个新的类型变量 U 并返回该类型,否则返回原有的类型 T。
对于 InferredAb<{ a :number, b: number}>
来说,因为 a 和 b 属性的类型都是 number,所以 abInferredNumber
也是 number 类型。但对于 InferredAb< { a :number, b: string}>
来说,
a 属性的类型是 number,这意味着 a: infer U
将返回 number 类型,而 b 属性的类型是 string,这意味着 b: infer U
将返回 string 类型。因此最终的返回类型将会是联合类型,即 number | string
。
四、参考资源
欢迎小伙伴们订阅全栈修仙之路,及时阅读 TypeScript、Node/Deno、Angular 技术栈最新文章。
