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. 关键点与注意事项
- 只能用于条件分支的
extends子句:infer只能在T extends SomePattern ? ... : ...的SomePattern中使用。 - 推断变量的作用域:
infer U声明的变量U只能在条件类型为true的分支(即?后面的部分)中使用。 - 多处推断同一变量:一个条件类型中可以在多处使用
infer U。TypeScript 会尝试统一所有U出现位置推断出的类型。如果无法统一(例如推断出string和number),结果可能是string & number(即never)或根据具体位置产生联合类型。// 这个例子尝试从两个位置推断同一个 U type Same<T> = T extends [infer U, infer U] ? U : never; type Test = Same<[string, number]>; // string | number (结果是联合类型,因为类型位置不同) - 与
never的关系:如果模式匹配失败,infer声明的变量不会被实例化,条件类型的结果由false分支决定。 - 配合
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[]]),你可以精确地从复杂的数组或元组结构中推导出所需的类型信息。

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