0Pricing
TypeScript Academy · 课时

类型级递归

使用递归条件类型遍历类型。

类型级递归 是 CoddyKit 上的免费 TypeScript Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 TypeScript Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 TypeScript Academy 课程共包含 4 节课。

类型中的递归

条件类型可以引用自身,从而让类型语言拥有循环。大多数类型级递归会逐个元素遍历元组,拆下头部,再对尾部递归。

type Length<T extends unknown[]> =
  T extends [unknown, ...infer Rest]
    ? Length<Rest>
    : 0;
// (this counts down to a base case)

基本情况

每次递归都需要一个停止条件。对于元组来说,通常就是空元组。当模式 [head, ...rest] 不再匹配时,就表示已经到达末尾,此时返回一个固定结果。

type IsEmpty<T extends unknown[]> =
  T extends [] ? true : false;

type A = IsEmpty<[]>;     // true
type B = IsEmpty<[1, 2]>; // false

头部和尾部

核心操作是使用带有展开模式的 infer,将元组拆分为第一个元素和剩余的元组。

type Head<T extends unknown[]> = T extends [infer H, ...unknown[]] ? H : never;
type Tail<T extends unknown[]> = T extends [unknown, ...infer R] ? R : [];

type A = Head<[1, 2, 3]>; // 1
type B = Tail<[1, 2, 3]>; // [2, 3]

逐个处理元素

将头部、尾部和递归结合起来,就可以转换整个元组。ToStrings 会重新构建元组,将每个元素类型替换为 string。

type ToStrings<T extends unknown[]> =
  T extends [infer H, ...infer R]
    ? [string, ...ToStrings<R>]
    : [];

type A = ToStrings<[1, true, 3]>; // [string, string, string]

累加器模式

您通常会在一个名为累加器的额外参数中逐步构建结果。它从空值开始,在每一步中不断增长。这样可以避免重复遍历结构,是类型级循环的标准技巧。

type Reverse<T extends unknown[], Acc extends unknown[] = []> =
  T extends [infer H, ...infer R]
    ? Reverse<R, [H, ...Acc]>
    : Acc;

type A = Reverse<[1, 2, 3]>; // [3, 2, 1]

遍历累加器

跟踪 Reverse<[1,2,3]>:

  • 步骤 1:头部为 1,累加器变为 [1]
  • 步骤 2:头部为 2,累加器变为 [2,1]
  • 步骤 3:头部为 3,累加器变为 [3,2,1]
  • 元组为空,返回累加器 = [3,2,1]

累加器会在每次递归调用中传递答案。

type R = Reverse<["a", "b"]>; // ["b", "a"]

对联合类型递归

您也可以通过递归将字符串联合起来。在这里,我们将元组元素连接成一个字符串字面量类型,并使用分隔符分开。

表示法:实际的 TypeScript 会使用由反引号分隔、包含美元符号大括号占位符的字符串来书写模板字面量类型。在这些代码片段中,我们将这种模式表示为 Tpl<...>,按顺序列出各个部分;例如,与字面量前缀和随后内容 Rest 匹配的反引号模板会表示为 Tpl<'prefix', infer Rest>。

type Join<T extends string[], Sep extends string = ','> =
  T extends [infer H extends string, ...infer R extends string[]]
    ? R extends []
      ? H
      : Tpl<H, Sep, Join<R, Sep>>
    : '';
// Tpl<H, Sep, ...> builds a template literal type joining the parts

type A = Join<['a', 'b', 'c']>; // 'a,b,c'

使用递归计数

递归加上累加器就可以进行计数。构建一个与输入长度相同、由 unknown 组成的元组,然后读取它的 length。您将在算术课程中大量使用这一技巧。

type Count<T extends unknown[], Acc extends unknown[] = []> =
  T extends [unknown, ...infer R]
    ? Count<R, [unknown, ...Acc]>
    : Acc["length"];

type A = Count<["x", "y", "z"]>; // 3

使用递归筛选

遍历元组,只保留符合条件的元素,丢弃其他元素。只要不将某个元素加入累加器,就可以跳过它。

type KeepStrings<T extends unknown[], Acc extends unknown[] = []> =
  T extends [infer H, ...infer R]
    ? H extends string
      ? KeepStrings<R, [...Acc, H]>
      : KeepStrings<R, Acc>
    : Acc;

type A = KeepStrings<[1, "a", 2, "b"]>; // ["a", "b"]

递归深度限制

编译器会限制递归深度(历史上大约为 50,但尾递归优化可以让某些模式达到更高深度)。对于非常大的元组,您可能会遇到类型实例化过深的问题。请使用累加器(尾递归)风格来提高这个上限。

type Repeat<T, N extends number, Acc extends T[] = []> =
  Acc["length"] extends N ? Acc : Repeat<T, N, [...Acc, T]>;

type A = Repeat<0, 3>; // [0, 0, 0]

整合运用

头部与尾部拆分、基本情况和累加器,是几乎所有类型级循环的三个组成部分。借助它们,您可以完全在类型系统中映射、筛选、反转、计数和连接元组。

type MapToPairs<T extends unknown[], Acc extends unknown[] = []> =
  T extends [infer H, ...infer R]
    ? MapToPairs<R, [...Acc, [H, H]]>
    : Acc;

type A = MapToPairs<[1, 2]>; // [[1, 1], [2, 2]]

快速检查

测试您对累加器模式的理解。

回顾

现在,您已经可以在类型级别编写循环。

  • 自引用条件类型会创建递归。
  • 将元组拆分为 [H, ...R],然后对尾部递归。
  • 基本情况(空元组)会停止循环。
  • 累加器会构建结果,并支持更深层的递归。

接下来:条件类型在联合类型上的特殊行为。

常见问题解答

「类型级递归」课时是免费的吗?

是的 — 「类型级递归」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 TypeScript Academy 课程的其余内容,请升级到 CoddyKit PRO。 TypeScript Academy 课程共包含 4 节课。

「类型级递归」这节课中我会学到什么?

使用递归条件类型遍历类型。 你通过在浏览器中直接运行的动手代码来练习 TypeScript Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 TypeScript Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 TypeScript Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「类型级递归」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 TypeScript Academy 课中编写并运行代码吗?

能。每节 TypeScript Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 作为计算语言的类型
  2. 类型级条件
  3. 类型级递归
  4. 可分配条件类型
← 返回 TypeScript Academy