Appearance
问题
- 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;
关键点解释:
A extends A
是为了触发分布式行为(即对联合类型中的每个成员分别执行)([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 的高级类型系统吧!🚀