第 14 章 · 元组与数组体操
- 掌握元组的「拆」与「拼」:
[F, ...R]、[...R, L],实现增删改查 - 实现元组反转、弹出、位移等「顺序变换」
- 用累积参数实现查找(
IndexOf)与去重(Unique) - 理解「元组长度 ⇄ 数字」的互相转换,这是第 17 章数学体操的地基
- 学会判断「是不是元组」(
IsTuple)
14.1 元组的基本拆拼
第 5 章见过 [F, ...R] 拆头、[...R, L] 拆尾。这两个动作是本章所有操作的原语:
// 拆头:取第一项 + 剩余
type PopHead<T extends any[]> = T extends [infer F, ...infer R] ? F : never
type FirstOf<[1, 2, 3]> // 1
// 拆尾:取最后一项 + 其余
type PopTail<T extends any[]> = T extends [...infer R, infer L] ? L : never
type LastOf<[1, 2, 3]> // 3
// 拼头 / 拼尾(第 5 章的 Push / Unshift 正是它们)
type Push<T extends any[], U> = [...T, U]
type Unshift<T extends any[], U> = [U, ...T]
[F, ...R] 拆出来的东西,用 [F, ...R] 就能拼回去。所有元组变换都是「拆几步 → 重组」。
14.2 顺序变换:Pop / Shift / Reverse
14.2.1 Pop 与 Shift:去掉一头
// 去掉最后一项
type Pop<T extends any[]> = T extends [...infer R, any] ? R : []
type A = Pop<[1, 2, 3]> // [1, 2]
// 去掉第一项
type Shift<T extends unknown[]> = T extends [unknown, ...infer R] ? R : []
type B = Shift<[1, 2, 3]> // [2, 3]
注意 Pop 用 [..., infer R, any] 把「最后一个位置」占掉,剩下 R——这就是「弹出」的模型。
14.2.2 Reverse:累积参数反转
用「累积 + 头插」反转,是最直观的写法:
type Reverse<T extends any[], R extends any[] = []> =
T extends [infer F, ...infer Rest]
? Reverse<Rest, [F, ...R]> // 把 F 插到 R 前面
: R
type A = Reverse<['a', 'b', 'c']> // ['c', 'b', 'a']
[F, ...R](头插)会反转;[...R, F](尾插)会保持顺序。想清楚你要哪种顺序,就选哪种拼法。
14.3 查找与去重
14.3.1 IndexOf:位置 + Equal
IndexOf<T, U> 返回 U 在 T 中的下标(没有则 -1)。用累积参数数下标:
type Equal<X, Y> = ( /* 第 12 章的严格相等 */ )
type IndexOf<T, U, C extends any[] = []> =
T extends [infer F, ...infer Rest]
? Equal<F, U> extends true
? C['length'] // 找到了 → 返回当前计数
: IndexOf<Rest, U, [...C, any]> // 没找到 → 计数 +1,继续
: -1
type A = IndexOf<[2, 6, 3], 3> // 2
type B = IndexOf<[2, 6, 3], 9> // -1
C extends any[] = [] 每递归一层就 [...C, any],于是 C['length'] 就是「当前到第几层」——这正是「元组长度当计数器」的用法,第 17 章的数学全建立在它上面。
14.3.2 Unique:去重(复习第 13 章)
type Includes<Arr extends any[], U> = /* 13.3.1 */
type Unique<T extends any[], R extends any[] = []> =
T extends [infer F, ...infer Rest]
? Includes<R, F> extends true
? Unique<Rest, R>
: Unique<Rest, [...R, F]>
: R
Pop/Shift/Reverse 是纯拆拼;IndexOf 加了 Equal + 计数;Unique 是 Includes + 累积;IsTuple 则是元组 vs 数组的判定。由易到难,正好把本章的手法串一遍。
14.4 IsTuple:元组 vs 数组
「是不是元组」的判定很实用。元组和数组的区别在于 length:
- 元组的
length是字面量(1、2、5……) - 数组的
length是number
type IsTuple<T> =
[T] extends [never] // 先排除 never
? false
: T extends readonly any[] // 必须长得像数组/元组
? number extends T['length'] ? false : true // length 是 number → 数组;是字面量 → 元组
: false
type A = IsTuple<[1, 2]> // true
type B = IsTuple<number[]> // false(length 是 number)
type C = IsTuple<{ length: 1 }> // false(不是数组形状)
never 会「extends 任何东西」,如果直接走后面的判断,IsTuple<never> 会误判。先用 [T] extends [never] 挡一层是标准防御。
14.5 元组长度 ⇄ 数字
这是本章最重要的一节,也是第 17 章的地基。
14.5.1 数字 → 元组:BuildTuple
type BuildTuple<N extends number, T extends any[] = []> =
T['length'] extends N ? T : BuildTuple<N, [...T, any]>
type A = BuildTuple<3> // [any, any, any]
14.5.2 元组 → 数字:直接取 length
type B = [any, any, any]['length'] // 3
14.5.3 数字运算 = 元组长度运算
// 两个数字相加:把两个元组拼起来取长度
type Add<A extends number, B extends number> =
[...BuildTuple<A>, ...BuildTuple<B>]['length']
type S = Add<2, 3> // 5
// 两个数字相减:长的元组吞掉短的,剩多少就是差
type Subtract<M extends number, S extends number> =
BuildTuple<M> extends [...BuildTuple<S>, ...infer R] ? R['length'] : never
type D = Subtract<5, 2> // 3
这种「用元组长度当数字」的办法,上限受递归深度限制(第 13 章 13.5)。小数字没问题,MinusOne<1000> 附近就开始吃力,再大就要换字符串算术(第 17 章)。理解它的原理,比记住上限更重要。
本章小结
- 元组原语:
[F, ...R]拆头、[...R, L]拆尾、[...T, U]拼尾、[U, ...T]拼头 Pop/Shift/Reverse是纯拆拼;Reverse用「头插累积」反转IndexOf= 拆 + Equal + 累积计数;Unique= Includes + 累积IsTuple用「length 是字面量还是 number」判定- 元组长度 ⇄ 数字互相转换,
Add/Subtract就此实现——第 17 章的地基
本章练习
挑战题
| 题号 | 题目 | 难度 | 考察点 |
|---|---|---|---|
| 00016 | 排除最后一项 | medium | [...R, any] 拆尾 |
| 03192 | Reverse | medium | 头插累积 |
| 03062 | Shift | medium | [unknown, ...R] 拆头 |
| 04484 | IsTuple | medium | length 判定 |
| 05153 | IndexOf | medium | Equal + 计数 |
| 05360 | Unique | medium | Includes + 累积 |
代码练习
-
Pop/Shift 变体:实现
Pop和Shift后,再实现Push、Unshift,验证四个方向都能「加/减一头」。 -
IndexOf 计数:不照抄 14.3.1,自己用累积参数实现
IndexOf,验证IndexOf<[string, 1, number], number>是2。 -
Reverse 双版本:分别用
[F, ...R](头插)和[...R, F](尾插)实现反转,观察结果差异,说明为什么头插才反转。 -
Add / Subtract:实现
Add<A, B>和Subtract<M, S>(14.5.3),验证Add<4, 7>是11、Subtract<10, 4>是6。 -
IsTuple 组合:用
IsTuple写type FirstOr<T, D>:如果是元组返回第一项,否则返回默认值D(提示:IsTuple<T> extends true ? ... : D)。