Skip to content

问题

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}`;

这是最基础的组合逻辑。给定两个字符串类型 AB,返回以下四种可能:

  • 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 的高级类型系统吧!🚀