infer 关键字
在上面的例子中,假如不再比较填充的函数类型是否是 (...args: any[]) => string 的子类型,而是要拿到其返回值类型呢?
TypeScript 中支持通过 infer 关键字来在条件类型中提取类型的某一部分信息。
type FunctionReturnType<T extends Func> = T extends (
...args: any[]
) => infer R
? R
: never;
上面的代码表达了当传入的类型参数满足 T extends (...args: any[] ) => infer R 这样一个结构,返回 infer R 位置的值,即 R。否则,返回 never。
infer 是 inference 的缩写,意为推断,如 infer R 中 R 就表示 待推断的类型。
infer 只能在条件类型中使用。
这里的类型结构并不局限于函数类型结构,还可以是数组:
type Swap<T extends any[]> = T extends [infer A, infer B] ? [B, A] : T;
type SwapResult1 = Swap<[1, 2]>; // 符合元组结构,首尾元素替换[2, 1]
type SwapResult2 = Swap<[1, 2, 3]>; // 不符合结构,没有发生替换,仍是 [1, 2, 3]
由于声明的结构是一个仅有两个元素的元组,因此三个元素的元组就被认为是不符合类型结构了。但可以使用 rest 操作符来处理任意长度的情况:
// 提取首尾两个
type ExtractStartAndEnd<T extends any[]> = T extends [
infer Start,
...any[],
infer End
]
? [Start, End]
: T;
// 调换首尾两个
type SwapStartAndEnd<T extends any[]> = T extends [
infer Start,
...infer Left,
infer End
]
? [End, ...Left, Start]
: T;
// 调换开头两个
type SwapFirstTwo<T extends any[]> = T extends [
infer Start1,
infer Start2,
...infer Left
]
? [Start2, Start1, ...Left]
: T;
infer 甚至可以和 rest 操作符一样同时提取一组不定长的类型,而 ...any[] 的用法是否也让你直呼神奇?
上面的输入输出仍然都是数组,而实际上完全可以进行结构层面的转换。比如从数组到联合类型:
type ArrayItemType<T> = T extends Array<infer ElementType> ? ElementType : never;
type ArrayItemTypeResult1 = ArrayItemType<[]>; // never
type ArrayItemTypeResult2 = ArrayItemType<string[]>; // string
type ArrayItemTypeResult3 = ArrayItemType<[string, number]>; // string | number
原理即是这里的 [string, number] 实际上等价于 (string | number)[]。
除了数组,infer 结构也可以是接口:
// 提取对象的属性类型
type PropType<T, K extends keyof T> = T extends { [Key in K]: infer R }
? R
: never;
type PropTypeResult1 = PropType<{ name: string }, 'name'>; // string
type PropTypeResult2 = PropType<{ name: string; age: number }, 'name' | 'age'>; // string | number
// 反转键名与键值
type ReverseKeyValue<T extends Record<string, unknown>> = T extends Record<infer K, infer V> ? Record<V & string, K> : never
type ReverseKeyValueResult1 = ReverseKeyValue<{ 'key': 'value' }>; // { "value": "key" }
为了体现 infer 作为类型工具的属性,结合了索引类型与映射类型,以及使用 & string 来确保属性名为 string 类型的小技巧。
为什么需要这个小技巧,如果不使用又会有什么问题呢?
// 类型“V”不满足约束“string | number | symbol”。
type ReverseKeyValue<T extends Record<string, string>> = T extends Record<
infer K,
infer V
>
? Record<V, K>
: never;
明明约束已经声明了 V 的类型是 string,为什么还是报错了?
这是因为泛型参数 V 的来源是从键值类型推导出来的,TypeScript 中这样对键值类型进行 infer 推导,将导致类型信息丢失,而不满足索引签名类型只允许 string | number | symbol 的要求。
这里需要同时满足其两端的类型,使用 V & string 这一形式,就确保了最终符合条件的类型参数 V 一定会满足 string | never 这个类型,因此可以被视为合法的索引签名类型。
infer 结构还可以是 Promise 结构。
type PromiseValue<T> = T extends Promise<infer V> ? V : T;
type PromiseValueResult1 = PromiseValue<Promise<number>>; // number
type PromiseValueResult2 = PromiseValue<number>; // number,但并没有发生提取
像条件类型可以嵌套一样,infer 关键字也经常被使用在嵌套的场景中,包括对类型结构深层信息地提取,以及对提取到类型信息的筛选等。
比如上面的 PromiseValue,如果传入了一个嵌套的 Promise 类型就失效了:
type PromiseValueResult3 = PromiseValue<Promise<Promise<boolean>>>; // Promise<boolean>,只提取了一层
这时就需要进行嵌套地提取了:
type PromiseValue<T> = T extends Promise<infer V>
? V extends Promise<infer N>
? N
: V
: T;
也可以使用递归来处理任意嵌套深度:
type PromiseValue<T> = T extends Promise<infer V> ? PromiseValue<V> : T;
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论