递归深度与限制
理解 TypeScript 的递归深度限制。
递归深度与限制 是 CoddyKit 上的免费 TypeScript Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 TypeScript Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 TypeScript Academy 课程共包含 4 节课。
TypeScript 有递归限制
递归类型功能强大,但类型检查器会限制实例化它们的深度。达到该限制后,就会出现深度错误。
// Excessively deep recursive types can produce:
// "Type instantiation is excessively deep and possibly infinite."深度错误的来源
如果条件类型在递归时没有明确且不断缩小的基准情况,就可能不断扩展,尤其是在与大型联合类型或元组构建结合时。
type Repeat<N extends number, Acc extends unknown[] = []> =
Acc["length"] extends N ? Acc : Repeat<N, [...Acc, unknown]>;
// Large N can exceed the instantiation depth.
type Five = Repeat<5>;安全的有界递归
保持递归浅且有界。构建小型元组没有问题;基准情况 Acc["length"] extends N 会及时停止递归。
type Tuple<N extends number, Acc extends unknown[] = []> =
Acc["length"] extends N ? Acc : Tuple<N, [...Acc, unknown]>;
type Three = Tuple<3>; // [unknown, unknown, unknown]
const t: Three = [1, 2, 3];
console.log(t.length);尾递归类型模式
TypeScript 会通过携带累加器来优化某些尾递归条件类型,使其能够比朴素的嵌套方式进行更深的递归。
type Reverse<T extends unknown[], Acc extends unknown[] = []> =
T extends [infer Head, ...infer Tail]
? Reverse<Tail, [Head, ...Acc]>
: Acc;
type R = Reverse<[1, 2, 3]>; // [3, 2, 1]
const r: R = [3, 2, 1];
console.log(r);累加器为何有用
通过累加器向前传递结果,可以使递归处于尾部位置;相比深度嵌套的条件类型,TypeScript 能更高效地展开这种递归。
type Join<T extends string[], Acc extends string = ""> =
T extends [infer H extends string, ...infer R extends string[]]
? Join<R, Acc extends "" ? H : Acc>
: Acc;
type First = Join<["a", "b", "c"]>; // "a"
const f: First = "a";
console.log(f);避免非尾递归
将递归调用包裹在另一个类型操作中会破坏尾部位置,并可能更早触及限制。请改为在累加器中携带状态。
// Non-tail (can be costly): builds nesting around the recursive call
// type Bad<T> = T extends [infer H, ...infer R] ? [H, ...Bad<R>] : [];
// Tail-friendly alternative uses an accumulator parameter.
console.log("prefer accumulators");明确限制深度
添加深度计数器,使类型在固定层数后停止,以完整性换取确定的终止。
type Flatten<T, Depth extends unknown[] = []> =
Depth["length"] extends 5
? T
: T extends (infer U)[]
? Flatten<U, [...Depth, unknown]>
: T;
type X = Flatten<number[][]>; // number
const x: X = 7;
console.log(x);实际深度通常没有问题
日常使用的递归类型、链表、树和 JSON 几乎不会触及该限制,因为实例化的数据很浅。限制主要影响繁重的类型级计算。
type Json = string | number | boolean | null | Json[] | { [k: string]: Json };
const v: Json = { a: { b: { c: 1 } } }; // fine, shallow
console.log(v);识别错误消息
当您看到“深度过深且可能是无限的”时,请检查是否缺少基准情况、累加器处于非尾部位置,或输入过大。
// Fixes: add/tighten the base case, switch to tail recursion,
// reduce input size, or cap with a depth counter.
console.log("check base case and tail position");运行时递归是另一回事
这些限制针对的是类型级递归。基于递归类型的普通递归函数会在运行时执行,其限制因素只有调用栈。
type TreeNode<T> = { value: T; children: TreeNode<T>[] };
function count<T>(n: TreeNode<T>): number {
return 1 + n.children.reduce((a, c) => a + count(c), 0);
}
console.log(count({ value: 1, children: [{ value: 2, children: [] }] }));在限制范围内设计
优先使用浅层数据、带累加器的尾递归模式以及明确的深度上限。养成这些习惯后,您在实际代码中很少会遇到该限制。
type Length<T extends unknown[]> = T["length"];
type N = Length<[1, 2, 3]>; // 3
const n: N = 3;
console.log(n);快速检查:递归限制
请测试您对递归深度和限制的理解。
回顾:递归深度和限制
您了解到 TypeScript 会限制类型级递归的深度,带累加器的尾递归模式和明确的深度上限会有所帮助,而日常使用的递归数据很少触及该限制。
type Reverse<T extends unknown[], Acc extends unknown[] = []> =
T extends [infer H, ...infer R] ? Reverse<R, [H, ...Acc]> : Acc;
const r: Reverse<[1, 2]> = [2, 1];
console.log(r);常见问题解答
「递归深度与限制」课时是免费的吗?
是的 — 「递归深度与限制」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 TypeScript Academy 课程的其余内容,请升级到 CoddyKit PRO。 TypeScript Academy 课程共包含 4 节课。
「递归深度与限制」这节课中我会学到什么?
理解 TypeScript 的递归深度限制。 你通过在浏览器中直接运行的动手代码来练习 TypeScript Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 TypeScript Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 TypeScript Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「递归深度与限制」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 TypeScript Academy 课中编写并运行代码吗?
能。每节 TypeScript Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。