【翻译】理解TypeScript中的infer
原文:Understanding infer in TypeScript
大家都用过不爱写类型的库。就以下面这个这个第三方函数为例:
1 | function describePerson(person: { |
如果库里没给describePerson
的参数person
单独写个类型,那TypeScript会无法正确推断定义的person
变量的类型。
1 | const alex = { |
TypeScript会将alex
的类型推断为{ name: string; age: number; hobbies: string[] }
且不允许其作为describePerson
的参数。
而且,就算它允许,也最好对alex
做个类型检查,确保有正确的自动补全。归功于TypeScript的infer
关键词,这其实很容易做到。
1 | const alex: GetFirstArgumentOfAnyFunction<typeof describePerson> = { |
TypeScript中的infer
关键词与条件类型允许我们将一个类型与其任意部分进行隔离后使用。
没有值的never
类型
在TypeScript里,never
被当作“没有值”的类型。你将会经常看到它作为一个dead-end类型。一个联合类型,比如string | never
,在TypeScript中与string
等价,忽略never
。
方便理解,你可以把string
和never
当作数学集合,其中string
是个包含了所有字符串值的集合,never
是个没有值的集合(∅空集)。这样两个集合的并集,显然是前者。
反之,string | any
的组合结果为any
。同样,你可以将这当成string
集合与包含所有集合的全集(U)的组合,显而易见的,等价于其自身。
这就解释了为什么将never
作为出口,因为它在与其他类型组合后将消失。
在TypeScript中使用条件类型
条件类型通过对特性约束的满足与否来对类型进行操作。看起来像是JavaScript里的三元运算符。
extends
关键词
在TypeScript中,通过extends
关键词来表达约束。T extends K
意为可以安全地假设一个类型为T
的值同时也类型为K
。例如0 extends number
是成立的,因为var zero: number = 0
是类型安全的。
因此,我们可以检查一个泛型是否满足约束,依此返回不同的类型。
StringFromType
返回一个接受参数的原始类型的字符串:
1 | type StringFromType<T> = T extends string ? 'string' : never |
译注
lorem
类型为’string’,该类型名为’string’,值只可为’string’。
为了让StringFromType
的泛型覆盖更全面,我们可以连接更多条件,就像JavaScript里嵌套的三元运算符一样。
1 | type StringFromType<T> = T extends string |
条件类型与联合
将一个联合类型拓展成一个约束,TypeScript会将遍历联合的成员并返回联合自身:
1 | type NullableString = string | null | undefined |
TypeScript将会通过遍历联合string | null | undefined
来测试约束T extends null | undefined
,一次一个类型。
你可以将过程看作下面这段说明代码:
1 | type stringLoop = string extends null | undefined ? never : string // string |
因为ReturnUnion
是一个string | never | never
的联合,它等价于string
(看上文说明)。
你可以看到TypeScript中的内置工具类型Extract
与Exclude
是如何从拓展后的联合中提取泛型来构建的:
1 | type Extract<T, U> = T extends U ? T : never |
条件类型与函数
检查一个类型是否派生自某个函数外观,一定不能用Function
类型。取而代之的,下面这个函数签名可以用于拓展所有可能的函数:
1 | type AllFunctions = (...args: any[]) => any |
译注
函数外观一词原文为 function shape ,概念与函数签名类似,故译作函数外观。
...args: any[]
能涵盖零个或多个参数,而=> any
能涵盖所有的返回类型。
在TypeScript中使用infer
infer
关键词补全了条件类型,且不能在extends
语句以外使用。infer
允许我们在约束中定义变量,以供引用或返回。
以TypeScript内置的ReturnType
工具为例。它接受一个函数类型并提供其返回类型:
1 | type a = ReturnType<() => void> // void |
其逻辑为先检查类型参数(T
)是否为函数,且在检查过程中通过infer R
将返回类型转为变量,然后在成功后返回:
1 | type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any; |
如前文所述,这在需要访问与使用那些不可用的类型时很有用。
React prop types
在React中,我们经常需要访问prop的类型。为此,React提供了一个工具类型用于访问prop类型,名为ComponentProps
,通过infer
实现。
1 | type ComponentProps< |
检查确认类型参数为React组件后,它将props推导并返回。如果失败,它将检查类型参数是否为IntrinsicElements
(div
,button
,等等)并返回其prop。如果都失败,返回{}
,它在TypeScript中意为“任意非空值”。
译注
{} 在TypeScript中的含义比较确切地说应该为:没有任何必要属性的类型。
infer
关键词的使用场景
使用infer
关键词经常被解释为展开一个类型。这里有一些infer
关键词的常用例子。
函数的第一个参数
这是我们第一个例子的解决方案:
1 | type GetFirstArgumentOfAnyFunction<T> = T extends ( |
函数的第二个参数
1 | type GetSecondArgumentOfAnyFunction<T> = T extends ( |
Promise的返回类型
1 | type PromiseReturnType<T> = T extends Promise<infer Return> ? Return : T |
数组的类型
1 | type ArrayType<T> = T extends (infer Item)[] ? Item : T |
总结
infer
关键词是TypeScript中一个允许我们在使用第三方代码时可以去展开与存储类型的有力工具。本文中,我们通过never
关键词,extends
关键词,联合,函数签名,对编写健壮条件类型的各个方面进行了阐述。