Skip to content

问题

  • IsUnion 差异
ts
type IsUnion<A> = A extends unknown[] ? never : A[] extends [A] ? false : true;
type IsUnion<A, B = A> = A extends A ? ([B] extends [A] ? false : true) : never;

回复

非常棒的问题!👏

你给出了两个版本的 IsUnion<T> 类型工具,它们都试图判断一个类型是否是联合类型(Union Type)

我们来详细分析这两个写法,并比较它们的优缺点:


✅ 两个版本对比

🧪 版本一(经典写法)

ts
type IsUnion<A> =
  A extends unknown[]
    ? never
    : A[] extends [A]
      ? false
      : true;

🧪 版本二(分布式写法)

ts
type IsUnion<A, B = A> =
  A extends A
    ? ([B] extends [A] ? false : true)
    : never;

🧠 实现原理详解

✅ 版本一:经典数组匹配法

原理回顾:

  • 利用 T[][T] 的差异来识别联合类型
  • 如果 T 是联合类型,则 T[] 不能赋值给 [T]
  • 如果 T 是非联合类型,则可以赋值

示例:

ts
type A = IsUnion<string | number>; // true
type B = IsUnion<string>;          // false
type C = IsUnion<number[]>;        // never(排除数组)

优点:

  • 简洁直观
  • 易于理解和教学
  • 社区广泛使用

缺点:

  • 对某些复杂联合类型可能误判(如函数联合类型)
  • 排除数组逻辑可能在某些场景下不适用

✅ 版本二:分布式条件类型 + 分布式技巧

原理回顾:

这是 TypeScript 中一种更高级的技巧,利用了分布式条件类型的特性

ts
type IsUnion<A, B = A> =
  A extends A
    ? ([B] extends [A] ? false : true)
    : never;
关键点解释:
  1. A extends A 是为了触发分布式行为(即对联合类型中的每个成员分别执行)
  2. ([B] extends [A] ? false : true)
    • 如果 A 是联合类型,那么在分布过程中,[B] extends [A] 会失败 → 返回 true
    • 如果 A 不是联合类型,那么 [B] extends [A] 成立 → 返回 false
示例:
ts
type A = IsUnion<1 | 2>;  // true
type B = IsUnion<'a'>;    // false
type C = IsUnion<never>;  // false
type D = IsUnion<[1,2]>;  // false

优点:

  • 更加“本质”地利用了联合类型的分布式特性
  • 可以处理一些版本一无法准确判断的边缘情况(如函数联合类型)
  • 不需要排除数组等特殊类型

缺点:

  • 理解门槛高(需要掌握分布式条件类型)
  • 对新手不太友好

🔍 深入理解版本二的机制

ts
type Test = IsUnion<'a' | 'b'>;

分解过程如下:

ts
'a' extends 'a' | 'b' → 是
=> [B] extends [A] → ['a' | 'b'] extends ['a'] → false
=> 所以返回 true

'b' extends 'a' | 'b' → 是
=> ['a' | 'b'] extends ['b'] → false
=> 所以返回 true

最终结果就是:true

而如果是非联合类型:

ts
type Test = IsUnion<'a'>;

=> 'a' extends 'a' → 是
=> ['a'] extends ['a'] → true
=> 返回 false

✅ 总结对比表

特性版本一(数组匹配)版本二(分布式技巧)
是否能正确判断联合类型✅ 是✅ 是
是否适用于函数联合类型❌ 否(可能误判)✅ 是
是否需要排除数组✅ 是❌ 否
是否易于理解✅ 是❌ 否
是否社区常用✅ 是⚠️ 高级用法
是否更精确❌ 一般✅ 更准确

📌 推荐使用哪个?

场景推荐版本
学习、教学、简单项目✅ 版本一(数组匹配)
生产环境、库开发、需要精确判断✅ 版本二(分布式技巧)

💡 补充:版本二的优化写法(更清晰)

为了语义更清晰,你可以这样写:

ts
type IsUnion<T, U = T> =
  T extends T
    ? ([U] extends [T] ? false : true)
    : never;

它等价于你写的那个版本,但变量名更有意义。


✅ 最后一句话总结:

版本二是更强大、更通用的写法,适合深入理解 TypeScript 类型系统的开发者;版本一更简洁直观,适合初学者和教学。


如果你想我帮你扩展以下功能,也可以告诉我 😎

例如:

  • 如何实现 IsIntersection<T>(判断交叉类型)
  • 如何提取联合类型中的某个子类型
  • 如何防止联合类型在条件类型中分布式展开(使用 [T] & {} 技巧)
  • 如何将联合类型转换为元组或函数参数列表等

继续深入玩转 TypeScript 的高级类型系统吧!🚀