返回介绍

infer 关键字

发布于 2024-09-11 00:55:49 字数 4034 浏览 0 评论 0 收藏 0

在上面的例子中,假如不再比较填充的函数类型是否是 (...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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文