Appearance
问题
ts
type Combination<A extends string, B extends string> =
| A
| B
| `${A}${B}`
| `${B}${A}`;
type AllCombinations<A extends string, B extends string = A> = A extends A
? Combination<A, AllCombinations<Exclude<B, A>>>
: never;
type AllCombinationsResult = AllCombinations<"A" | "B" | "C">;
// 详解一下
回答
非常棒的问题!👏
你提供的这段 TypeScript 类型代码:
ts
type Combination<A extends string, B extends string> =
| A
| B
| `${A}${B}`
| `${B}${A}`;
type AllCombinations<A extends string, B extends string = A> = A extends A
? Combination<A, AllCombinations<Exclude<B, A>>>
: never;
type AllCombinationsResult = AllCombinations<"A" | "B" | "C">;
它的目标是:递归地生成一个联合字符串类型的所有排列组合(permutations),包括单个字符和拼接后的结果。
🧠 我们的目标
我们要详细分析这行代码的执行过程,并解释最终结果是什么:
ts
type AllCombinationsResult = AllCombinations<"A" | "B" | "C">;
✅ 最终结果应为:
ts
"A" | "B" | "C" |
"AB" | "BA" | "AC" | "CA" | "BC" | "CB" |
"ABC" | "ACB" | "BAC" | "BCA" | "CAB" | "CBA"
即:
- 所有长度为 1 的字符
- 所有长度为 2 的排列组合
- 所有长度为 3 的排列组合
🔍 分步解析
我们来一步一步拆解这个类型是如何工作的。
第一步:理解 Combination<A, B>
的作用
ts
type Combination<A extends string, B extends string> =
| A
| B
| `${A}${B}`
| `${B}${A}`;
这是最基础的组合逻辑。给定两个字符串类型 A
和 B
,返回以下四种可能:
A
B
AB
BA
示例:
ts
type T = Combination<"A", "B">;
// 结果: "A" | "B" | "AB" | "BA"
第二步:理解 AllCombinations<A, B = A>
ts
type AllCombinations<A extends string, B extends string = A> =
A extends A
? Combination<A, AllCombinations<Exclude<B, A>>>
: never;
拆解一下:
A extends A
:这是一个技巧,用于触发分布式条件类型的特性。Exclude<B, A>
:从B
中排除已经用过的A
字符,避免重复使用同一个字符。Combination<A, AllCombinations<...>>
:将当前字符与剩余字符的所有组合进行拼接。
这个写法是一个经典的 递归 + 分布式条件类型,用于枚举所有排列组合。
🔄 分步展开示例
我们以输入为 "A" | "B" | "C"
来模拟展开过程:
Step 1: 分布式展开 A extends A
由于 A
是联合类型,它会被分别代入每个成员:
ts
A = "A" => Combination<"A", AllCombinations<Exclude<"A"|"B"|"C", "A">>
A = "B" => Combination<"B", AllCombinations<Exclude<"A"|"B"|"C", "B">>
A = "C" => Combination<"C", AllCombinations<Exclude<"A"|"B"|"C", "C">>
Step 2: 处理第一个分支:A = "A"
ts
Combination<"A", AllCombinations<Exclude<"A"|"B"|"C", "A">>
→ Combination<"A", AllCombinations<"B"|"C">>
继续递归:
ts
AllCombinations<"B"|"C"> →
B extends B ? Combination<"B", AllCombinations<Exclude<"B"|"C", "B">>
C extends C ? Combination<"C", AllCombinations<Exclude<"B"|"C", "C">>
展开 AllCombinations<"B"|"C">
ts
AllCombinations<"B"|"C"> →
"B" | "C" | "BC" | "CB"
所以:
ts
Combination<"A", "B" | "C" | "BC" | "CB">
→ "A" | ("B" | "C" | "BC" | "CB") | "AB" | "AC" | "ABC" | "ACB" | "BA" | "CA" | "BCA" | "CBA"
简化后得到:
ts
"A" | "B" | "C" | "AB" | "AC" | "ABC" | "ACB" | "BA" | "CA" | "BCA" | "CBA"
Step 3: 同样处理其他两个分支
对 A = "B"
和 A = "C"
,也会递归展开出类似的结果。
最终合并所有结果,你会得到:
ts
"A" | "B" | "C" |
"AB" | "BA" | "AC" | "CA" | "BC" | "CB" |
"ABC" | "ACB" | "BAC" | "BCA" | "CAB" | "CBA"
✅ 最终结果
ts
type AllCombinationsResult = AllCombinations<"A" | "B" | "C">;
/*
结果为:
"A" | "B" | "C" |
"AB" | "BA" | "AC" | "CA" | "BC" | "CB" |
"ABC" | "ACB" | "BAC" | "BCA" | "CAB" | "CBA"
*/
⚠️ 注意事项
- 这个类型只支持小范围的联合类型(如 3~4 个字符),否则会超出 TypeScript 的递归限制。
- 它不保证顺序,也不去重(如果有重复组合)。
- 如果你想控制最大长度、最小长度或去重,可以进一步封装。
💡 扩展玩法建议
你可以基于这个基础扩展出以下功能:
功能 | 实现思路 |
---|---|
限制最大长度 | 在递归中加深度计数器 |
去重 | 使用 UnionToTuple + 数组过滤 |
排列 vs 组合 | 当前是排列(考虑顺序),如果是组合(不考虑顺序),需调整逻辑 |
将联合类型转为元组 | 使用 UnionToTuple 工具 |
支持数字类型 | 先用 ToString<T> 转换 |
✅ 总结一句话:
你写的这个类型利用了 分布式条件类型 + 递归模板字面量类型,实现了在类型系统中对联合字符串的全排列组合计算。
如果你想我帮你扩展以下功能,也可以告诉我 😎
比如:
- 如何实现
Permutation<T>
(全排列) - 如何实现
UnionToTuple<T>
(将联合类型转为元组) - 如何实现
Unique<T>
(去重联合类型) - 如何实现
Subsets<T>
(子集)
继续深入玩转 TypeScript 的高级类型系统吧!🚀