IsUnion
IsUnion
题目
实现类型 IsUnion,判断传入的类型 T 是否为联合类型。
type case1 = IsUnion<string> // false
type case2 = IsUnion<string | number> // true
type case3 = IsUnion<[string | number]> // false解法
type IsUnion<T, U = T> = [T] extends [never]
? false
: T extends T
? [U] extends [T]
? false
: true
: never核心思路:"拷贝参数" + 分布式条件类型 + 非分布式比较三者联动。
逐步拆解
第一步 — 保留原始联合:U = T
在任何条件触发之前,把原始类型 T 备份到第二个参数 U。这一步至关重要——因为后续步骤会让 T 在分布时变成单个成员。
第二步 — 处理 never:[T] extends [never]
never 在语义上是空联合,但我们希望 IsUnion<never> 返回 false。用 [] 包裹禁止分布,让条件可以正常命中 false 分支。
第三步 — 触发分布:T extends T
T extends T 看起来是废话,但它会触发分布式条件类型,把联合的每个成员单独处理。当 T = string | number 时,TypeScript 展开为两条分支:
| 分支 | T(当前成员) | U(原始类型) |
|---|---|---|
| 1 | string | string | number |
| 2 | number | string | number |
第四步 — 比较成员与原始:[U] extends [T]
用 [] 包裹阻止再次分布。此时问题变为:原始类型能否 extends 当前单个成员?
T原本不是联合 →U === T(同一单类型)→[U] extends [T]为true→ 返回falseT原本是联合 →U是完整联合,T是单个成员 →[string | number] extends [string]为false→ 返回true
所有分支结果合并为联合,只要有一条分支出现了 true,最终就是 true。
深入理解
为什么"拷贝参数"能起作用?
关键在于:分布式条件类型只影响 被检查的类型参数(T extends Constraint 里的 T),其他变量(U)在所有分支中始终保持原始值。
// 当 T = string | number,进入 T = string 的分支时:
// U 仍然是 string | number ← 未被改变!
// [U] extends [T] → [string | number] extends [string] → false ✓没有 U,我们就无法引用分布前的原始联合,无法做这个比较。
[U] extends [T] 与 U extends T 的区别
裸写 U extends T 会再次触发对 U 的分布展开,结果乱掉。必须用元组包裹做非分布式的单次比较。
// ❌ 错误 — 会对 U 再次分布
type Check = U extends T ? false : true
// ✅ 正确 — 元组阻止分布
type Check = [U] extends [T] ? false : true外层 false : never 的作用
外层 T extends T 永远为真,never 分支永远不会进入。它只是为了满足 TypeScript 条件类型的语法要求。
边界情况
IsUnion<never> // false — 被 [T] extends [never] 守卫
IsUnion<string> // false — 单类型,U === T
IsUnion<boolean> // true!boolean 内部是 true | false 的联合
IsUnion<string | never> // false — never 折叠:string | never = string
IsUnion<[string|number]> // false — 联合在元组内,T 本身不是联合boolean 这个边界案例很有趣:TypeScript 内部把 boolean 表示为 true | false 的联合,所以 IsUnion<boolean> 正确返回 true。
核心要点
- 拷贝参数模式(
U = T):在分布改变T之前保留原始类型的引用 T extends T:触发分布式条件类型,遍历联合成员的惯用写法[U] extends [T]:元组包裹 = 非分布式比较,将整体联合与单个成员对照[T] extends [never]:检测never而不触发分布的标准守卫写法- TypeScript 的
boolean是true | false的语法糖——别忘了这个隐藏联合
