☰
TS 类型体操:一个递归模型,拆完 type-challenges 八道 Medium 题
2026/10/5 2:24:54 网站建设 项目流程

TS 类型体操:一个递归模型,拆完 type-challenges 八道 Medium 题

【免费下载链接】weekly前端精读周刊。帮你理解最前沿、实用的技术。项目地址: https://gitcode.com/GitHub_Trending/we/weekly

TypeScript 类型体操里最劝退的,往往不是语法,而是"条件类型 + infer + 递归"三连。我把 type-challenges 题库的 Medium 17~24 这八道题(Permutation、LengthOfString、Flatten、AppendToObject、Absolute、StringToUnion、Merge、KebabCase)逐道抠完,发现它们共用一套很土的递归模型:把输入拆出一个最小单元,存进一个累加器,让输入变小,再递归,直到空了取累加器——说白了就是把 JS 里的一个for循环搬到了类型层面。

这篇 type-challenges 题解按这套模型把八道题分成三组拆完,不预设你读过别的解法;每到一个卡点,我会先把"你以为会怎样"摆出来,再告诉你哪里想当然,最后给正解。

一、先把 for 循环搬进类型层:拆、存、缩

你写 JS 循环大概是这个形状:

let acc: any[] = []; for (let i = 0; i < input.length; i++) { acc.push(process(input[i])); // 存一个 } return acc;

类型层面没有let,但有等价物——带默认值的第二个泛型。它在递归里扮演的就是这个acc,我后面会反复叫它"累加器"。把循环翻译成类型,骨架是:

// 把"逐项处理"翻译成类型递归 type Loop<First, Rest, Acc extends unknown[] = []> = Rest extends [infer First, ...infer R] ? Loop<First, R, [...Acc, First]> // 存一个(Acc),缩一个(R) : Acc; // 空了,取累加器

三个角色,后面八题全是它的变体,先记住名字:

  • First:这一步拆出来的最小单元
  • Acc:累加器,默认值([]或never或''),把中间态攒着
  • R:缩小后的输入,递归它

模型立完,下面按输入长什么样分三组。

二、输入是联合类型:逼出分配律,排满每一种可能

题干

type perm = Permutation<'A' | 'B' | 'C'>; // ['A','B','C'] | ['A','C','B'] | ['B','A','C'] | ['B','C','A'] | ['C','A','B'] | ['C','B','A']

破题:卡点是"怎么让一个入参递归下去"。TS 有个分配行为——当一个裸泛型出现在T extends U ? A : B里,且T本身是联合类型时,TS 会把T拆成单个成员分别判断,再把结果用|拼回来:

type IsX<T> = T extends 'x' ? 1 : 2; type R = IsX<'a' | 'b'>; // = IsX<'a'> | IsX<'b'> = 2

Permutation 正需要这个"拆":递归时把'A'|'B'|'C'摊成'A'、'B'、'C'各自往下走。但分配只在"裸泛型"上触发,而题面只给了一个入参。解法是再声明一个默认值等于第一个的泛型,凑出第二个位置让分配有处下手:

// T:当前要占坑的成员;U:还没排完的全部成员(默认 = T) type Permutation<T, U = T> = [T] extends [never] ? [] // 没人可排,终止 : T extends U ? [T, ...Permutation<Exclude<U, T>>] // 占 T 的坑,剩余成员继续排 : [];

这里有个 🔴 想当然的坑。终止条件很多人会写成T extends never ? [] : ...,跑出来直接never,或者压根不展开。原因:TS 对T extends ...做联合分配时有一条特例——当T恰好是never,它会跳过分配、把T原样返回,你的三元判断根本没执行。正解是用元组把T包一层:[T] extends [never]。包上之后分配行为被挡掉,判断才真正跑起来。这一招后面还会用到,先记住"判 never 一律用[T] extends [never]"。

手工展开一层(Permutation<'A'|'B'|'C'>):

T='A'|'B'|'C' 对 T 分配 ─┬─ Permutation<'A','A'|'B'|'C'> ├─ Permutation<'B','A'|'B'|'C'> └─ Permutation<'C','A'|'B'|'C'> 取第一支 Permutation<'A','A'|'B'|'C'>: 'A' extends 'A'|'B'|'C' → ['A', ...Permutation<'B

【免费下载链接】weekly前端精读周刊。帮你理解最前沿、实用的技术。项目地址: https://gitcode.com/GitHub_Trending/we/weekly

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询