Yuzhe's Blog

yuzhes

数组展平 Flatten

数组展平 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 是什么类型。

第三步 — First 是不是数组?

First extends unknown[]
  ? [...Flatten<First>, ...Flatten<Rest>]
  : [First, ...Flatten<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 解构。这个模式会在很多题目里出现:

掌握这个模式,就打通了元组操作类题目的任督二脉。

递归深度会不会有问题?

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 里,对尾递归更友好。两种写法都正确,各有侧重。

核心要点