数组展平 Flatten
大约 3 分钟
数组展平 Flatten
题目
在这个挑战中,你需要写一个接受 Array 并输出扁平化 Array 类型的类型。
type flatten = Flatten<[1, 2, [3, 4], [[[5]]]]>
// [1, 2, 3, 4, 5]解答
type Flatten<T extends unknown[]> = T extends [infer First, ...infer Rest]
? First extends unknown[]
? [...Flatten<First>, ...Flatten<Rest>]
: [First, ...Flatten<Rest>]
: []核心思路是递归解构元组:每次取出第一个元素,检查它是否是数组,如果是就继续展平,否则原样保留。
逐步拆解
第一步 — 终止条件:空元组
T extends [infer First, ...infer Rest]如果 T 是空数组 [],它匹配不上 [infer First, ...infer Rest],就会走到 : [] 分支返回空元组,递归在此终止。
第二步 — 拆分头部和尾部
对于 T = [1, 2, [3, 4], [[[5]]]]:
First=1Rest=[2, [3, 4], [[[5]]]]
然后判断 First 是什么类型。
第三步 — First 是不是数组?
First extends unknown[]
? [...Flatten<First>, ...Flatten<Rest>]
: [First, ...Flatten<Rest>]- 如果
First是数组 → 递归展平它,然后展开到结果里 - 如果
First是普通值 → 包成[First],继续递归处理Rest
[1, [2, [3]]] 的完整推导过程:
Flatten<[1, [2, [3]]]>
First=1(不是数组)→ [1, ...Flatten<[[2, [3]]]>]
First=[2,[3]](是数组)→ [...Flatten<[2,[3]]>, ...Flatten<[]>]
First=2(不是数组)→ [2, ...Flatten<[[3]]>]
First=[3](是数组) → [...Flatten<[3]>, ...Flatten<[]>]
First=3(不是数组)→ [3, ...Flatten<[]>]
Flatten<[]> = []
→ [3]
→ [3]
→ [2, 3]
→ [1, 2, 3]深入分析
为什么用 unknown[] 而不是 any[]?
用 T extends unknown[] 比 any[] 更安全。unknown 要求在使用前先做类型收窄,而 any 会悄悄跳过类型检查。虽然在这道题里没有实质区别,但能更好地传达意图:「我还不知道里面是什么,需要先检查。」
「展开 + infer」模式
T extends [infer Head, ...infer Tail] 是 TypeScript 中递归处理元组的基本范式。它对应函数式编程里的 head :: tail 解构。这个模式会在很多题目里出现:
Flatten(本题)Reverse、Last、Pop、Shift- 任何需要遍历元组的场景
掌握这个模式,就打通了元组操作类题目的任督二脉。
递归深度会不会有问题?
TypeScript 对递归深度有限制(大约 1000 层左右,复杂类型更少)。对于极深的嵌套数组 [[[[...]]]],可能会报错:
Type instantiation is excessively deep and possibly infinite.正常的嵌套数据不会触发这个问题。如果需要限制展平深度,可以参考 FlattenDepth(题目 3243),用一个累加器元组来计数。
另一种写法:累加器版本
也可以用一个 Acc 参数来收集结果:
type Flatten<
T extends unknown[],
Acc extends unknown[] = []
> = T extends [infer Head, ...infer Tail]
? Head extends unknown[]
? Flatten<Tail, [...Acc, ...Flatten<Head>]>
: Flatten<Tail, [...Acc, Head]>
: Acc这种写法把结果攒在 Acc 里,对尾递归更友好。两种写法都正确,各有侧重。
核心要点
[infer Head, ...infer Tail]是递归解构元组的标准写法First extends unknown[]用来判断某个元素本身是否是数组- 当
T匹配不上[First, ...Rest]时(即变成空数组),递归终止 - 同样的「递归展开」模式广泛应用于各种元组题目——学会一次,处处复用
