文章目录

TypeScript infer关键字在条件类型中的推导逻辑

发布于 2026-06-13 12:51:56 · 浏览 6 次 · 评论 0 条

TypeScript infer关键字在条件类型中的推导逻辑

在TypeScript的高级类型系统中,条件类型允许我们根据类型关系动态选择类型。infer关键字则是在此机制上实现类型推断的核心工具,它像一个“占位符”,让编译器在条件判断的某个位置自动推断并捕获一个类型。

掌握infer,意味着你能从复杂的类型结构中“提取”出你关心的部分,实现更灵活、更智能的类型编程。


1. 理解基本语法与定位

infer 只能在条件类型的 extends 子句中使用。它的作用是声明一个待推断的类型变量。

其基本结构如下:

type Extracted<T> = T extends SomeStructure<infer U> ? U : Fallback;

解读T extends SomeStructure<infer U> 这句话的意思是:

  1. 判断 T 是否是 SomeStructure<V> 的一个子类型。
  2. 如果是,那么 SomeStructure<V> 这个“模具”与 T 匹配的过程中,那个未知的 V 会被推断出来,并绑定到我们声明的变量 U 上。
  3. 最终,类型 U 就是条件为 true 时返回的类型。

简单来说,infer U 就是告诉TypeScript:“嘿,如果匹配成功,请把匹配到的那部分类型告诉我,我叫它 U。”


2. 核心使用场景与推导逻辑

我们将通过几个经典场景,一步步拆解 infer 是如何工作的。

场景一:提取函数返回类型

假设你想从一个函数类型中提取出它的返回值类型。这是最直观的用法。

// 定义一个工具类型,用于提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

// 示例
type Fn1 = () => string;
type Result1 = ReturnType<Fn1>; // 推断结果为 string

type Fn2 = (x: number) => boolean[];
type Result2 = ReturnType<Fn2>; // 推断结果为 boolean[]

推导逻辑拆解

  1. 匹配阶段T extends (...args: any[]) => infer RFn1 (() => string) 视为 (...args: any[]) => infer R 的候选者。
  2. 推断阶段:函数签名中,(...args: any[]) 匹配 Fn1 的参数部分(由于使用了 any[],它能匹配任何参数列表)。=> infer R 需要匹配 Fn1 的返回值部分。
  3. 绑定阶段:匹配成功,Fn1 的返回值 string捕获赋予推断变量 R
  4. 选择阶段:条件成立,返回 R,即 string

内置的 ReturnType<T> 工具类型正是基于此原理实现的。


场景二:提取Promise内部的类型

从一个 Promise<SomeType> 中提取 SomeType

type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type Test1 = UnwrapPromise<Promise<number>>; // number
type Test2 = UnwrapPromise<string>; // string (未匹配,返回T本身)

推导逻辑Promise<infer U> 试图匹配 Promise<number>infer U 占据了尖括号 <...> 内的位置,于是 number 被推断并绑定到 U


场景三:提取元组的第一个元素类型

type Head<T extends any[]> = T extends [infer First, ...any[]] ? First : never;

type Tuple = [string, number, boolean];
type FirstElement = Head<Tuple>; // string

推导逻辑[infer First, ...any[]] 是一个模式,它描述一个至少有一个元素的元组。infer First 匹配并捕获了元组的第一个元素类型。


场景四:深层嵌套提取(以提取对象嵌套属性类型为例)

假设你有一个嵌套对象类型,想安全地提取某个深层路径的类型。

type DeepGet<T, Path extends string[]> =
  Path extends [infer First, ...infer Rest]
    ? First extends keyof T
      ? Rest extends [] // 如果Rest为空,说明到达终点
        ? T[First]
        : DeepGet<T[First], Rest>
      : undefined
    : never;

interface Profile {
  name: string;
  address: {
    city: string;
    zip: number;
  };
}

// 提取 `Profile['address']['city']` 的类型 `string`
type CityType = DeepGet<Profile, ['address', 'city']>;

推导逻辑(递归过程)

  1. 第一次调用 DeepGet<Profile, ['address', 'city']>Path (['address', 'city']) 匹配 [infer First, ...infer Rest]First 被推断为 'address'Rest 被推断为 ['city']
  2. 检查 'address'Profilekey 吗?是的。并且 Rest (['city']) 不是空数组,所以递归调用 DeepGet<Profile['address'], ['city']>,即 DeepGet<{city: string; zip: number}, ['city']>
  3. 第二次调用Path (['city']) 匹配模式,First 推断为 'city'Rest 推断为 [](空元组)。
  4. 检查 'city'{city: string; zip: number}key 吗?是的。且 Rest 为空,到达终点,返回 T[First],即 {city: string; zip: number}['city'],结果为 string

3. 使用infer的关键点与限制

  1. 位置决定推断infer 声明的变量类型由其在模式中所处的位置决定。放在函数返回值位置,推断返回类型;放在泛型参数位置,推断泛型参数。
  2. 协变与逆变:当同一个位置被多次 infer 时(例如在联合类型中),推断出的类型是联合类型(协变)。
    type UnionInfer<T> = T extends { a: infer U; b: infer U } ? U : never;
    type Result = UnionInfer<{ a: string; b: number }>; // string | number (联合)

    在函数参数位置(逆变),推断出的类型是交叉类型

    type IntersectionInfer<T> = T extends {
      a: (x: infer U) => void;
      b: (x: infer U) => void;
    } ? U : never;
    type Result2 = IntersectionInfer<{
      a: (x: string) => void;
      b: (x: number) => void;
    }>; // string & number (交叉)
  3. 不可在“extends”子句外使用infer 必须紧跟在 extends 关键字后面的类型结构中。
  4. 多层嵌套推断:可以在复杂的嵌套结构中使用多个 infer,如 T extends Map<infer K, Set<infer V>>,同时推断键和值的类型。

4. 综合实战:构建一个Parameters工具类型

让我们自己动手实现一个提取函数参数类型的工具类型,加深理解。

// 错误示范:这样只能匹配固定参数数量的函数
// type MyParameters<T> = T extends (a: infer A) => any ? [A] : never;

// 正确实现:匹配任意参数列表
type MyParameters<T extends (...args: any) => any> = T extends (
  ...args: infer P
) => any
  ? P
  : never;

// 测试
type Fn = (x: string, y: boolean) => void;
type Params = MyParameters<Fn>; // [x: string, y: boolean]

推导逻辑

  1. T extends (...args: infer P) => any(...args: infer P) 是一个 rest 参数模式,它能匹配任意数量和类型的参数列表。
  2. 匹配 (x: string, y: boolean) => void 时,整个参数列表 [x: string, y: boolean] 作为一个元组类型被捕获并赋予 P
  3. 条件为真,返回 P,即参数元组类型。

至此,你已经掌握了 infer 在条件类型中的核心推导逻辑。它的本质是一种模式匹配与类型捕获。通过在条件类型的 extends 子句中定义模式并放置 infer 占位符,你可以引导TypeScript编译器去解构复杂的类型,并将你需要的部分提取出来用于后续的类型计算。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文