文章目录

TypeScript infer关键字在条件类型中推导数组元素类型的写法

发布于 2026-06-04 21:37:19 · 浏览 14 次 · 评论 0 条

TypeScript infer关键字在条件类型中推导数组元素类型的写法

掌握 infer 关键字在条件类型中的用法,是理解并编写高级 TypeScript 类型工具的关键一步。其核心作用是在条件类型的 extends 子句中声明一个待推断的类型变量。本文将专注于如何利用这一特性,从一个数组类型中“抓取”出其元素的类型。


1. 理解 infer 的基本语法

infer 只能出现在 extends 关键字后面的类型表达式中,用于捕获匹配部分的具体类型。

type ElementType<T> = T extends (infer U)[] ? U : never;
  • 定义一个条件类型 ElementType<T>
  • 检查 T 是否 (infer U)[] 的子类型。这里的 (infer U)[] 就是一个“模式”,它匹配任何数组类型。
  • 声明了一个待推断的类型变量 U。如果 T 匹配成功,U 就会被推断为数组元素的类型。
  • 返回 推断出的 U,否则返回 never

让我们测试一下:

type Numbers = ElementType<number[]>; // 推断结果为 number
type Strings = ElementType<string[]>; // 推断结果为 string
type UnionArray = ElementType<(string | number)[]>; // 推断结果为 string | number

这是最直接的应用:从 Type[] 形式的数组类型中提取出 Type


2. 处理更复杂的数组模式

infer 的强大之处在于其模式的灵活性,可以匹配并推断更复杂的结构。

2.1 推断只读数组的元素类型

只读数组的类型是 readonly T[]。我们可以用类似的方法处理。

type ReadonlyElementType<T> = T extends readonly (infer U)[] ? U : never;

type ReadonlyNums = ReadonlyElementType<readonly number[]>; // number

模式 readonly (infer U)[] 能匹配 readonly 修饰的数组类型。

2.2 处理元组类型

元组是已知元素数量和类型的数组。infer 同样可以从中提取信息。

// 提取元组第一个元素的类型
type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;

type FirstNum = First<[number, string, boolean]>; // number
type FirstStr = First<[string]>; // string
type EmptyFirst = First<[]>; // never
  • 模式 [infer F, ...any[]] 匹配至少有一个元素的元组(或数组)。
  • infer F 捕获第一个元素的类型。
  • ...any[] 表示剩余的元素(数量不限)。

2.3 推断数组最后元素的类型

这稍微复杂一些,需要递归。

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

type LastNum = Last<[string, number, boolean]>; // boolean
type LastStr = Last<[string]>; // string
  • 模式 [...any[], infer L] 匹配任何数组,infer L 捕获最后一个元素的类型。

3. 实际应用:创建实用的工具类型

理解了基础模式后,我们可以构建更实用的类型工具。

3.1 提取数组或 Promise 的值

一个常见的需求是统一处理可能包裹在 Promise 或数组中的类型。

type Unwrap<T> = T extends (infer U)[] ? U : T extends Promise<infer U> ? U : T;

type UnwrapArr = Unwrap<string[]>; // string
type UnwrapPromise = Unwrap<Promise<number>>; // number
type UnwrapDirect = Unwrap<boolean>; // boolean (不匹配前两个条件,返回 T 本身)

这个类型会“递归地”剥去数组或 Promise 的包装。

3.2 安全地提取嵌套数组的类型

有时数据结构是嵌套的数组,例如 number[][]。我们可以递归地提取最内层的元素类型。

type DeepElementType<T> = T extends (infer U)[]
  ? DeepElementType<U> // 如果 T 是数组,递归处理其元素类型 U
  : T; // 直到 T 不是数组,返回 T 本身

type Nested = DeepElementType<number[][][]>; // number
type NestedOr = DeepElementType<(string | boolean)[][]>; // string | boolean

这个递归类型会不断“剥开”外层的数组,直到找到最内层非数组的类型。


4. 关键点与注意事项

  1. 只能用于条件分支的 extends 子句infer 只能在 T extends SomePattern ? ... : ...SomePattern 中使用。
  2. 推断变量的作用域infer U 声明的变量 U 只能在条件类型为 true 的分支(即 ? 后面的部分)中使用。
  3. 多处推断同一变量:一个条件类型中可以在多处使用 infer U。TypeScript 会尝试统一所有 U 出现位置推断出的类型。如果无法统一(例如推断出 stringnumber),结果可能是 string & number(即 never)或根据具体位置产生联合类型。
    // 这个例子尝试从两个位置推断同一个 U
    type Same<T> = T extends [infer U, infer U] ? U : never;
    type Test = Same<[string, number]>; // string | number (结果是联合类型,因为类型位置不同)
  4. never 的关系:如果模式匹配失败,infer 声明的变量不会被实例化,条件类型的结果由 false 分支决定。
  5. 配合 extends 约束:你可以在推断时给 infer 变量添加约束。
    type ConstrainedInfer<T> = T extends (infer U extends string)[] ? U : never;
    type TestCI = ConstrainedInfer<('a' | 'b')[]>; // "a" | "b"
    type TestCI2 = ConstrainedInfer<number[]>; // never,因为 number 不满足 extends string

使用 infer 从数组中提取类型的核心在于构建匹配目标结构的“模式”,并用 infer 标记你想要捕获的部分。 通过组合基础模式(如 (infer U)[][infer F, ...any[]]),你可以精确地从复杂的数组或元组结构中推导出所需的类型信息。

评论 (0)

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

扫一扫,手机查看

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