注意:此答案适用于某人明确不想使用UnionToIntersection 的情况。那个版本简单易懂,所以如果你对U2I没有疑虑,那就去吧。
我刚刚又看了一遍,在@Gerrit0 的帮助下想出了这个:
// Note: Don't pass U explicitly or this will break. If you want, add a helper
// type to avoid that.
type IsUnion<T, U extends T = T> =
T extends unknown ? [U] extends [T] ? false : true : false;
type Test = IsUnion<1 | 2> // true
type Test2 = IsUnion<1> // false
type Test3 = IsUnion<never> // false
似乎可以进一步简化,对此我很满意。这里的诀窍是分发T 而不是U,以便您可以比较它们。所以对于type X = 1 | 2,你最终会检查[1 | 2] extends [1] 是否为假,所以这个类型总体上是true。如果T = never 我们也解析为false(感谢 Gerrit)。
如果类型不是联合,那么T 和U 是相同的,所以这个类型解析为false。
注意事项
在某些情况下这不起作用。由于T 的分布,具有可分配给另一个成员的任何联合都将解析为boolean。可能最简单的例子是{} 在联合中,因为几乎所有东西(甚至是原语)都可以分配给它。您还将看到联合包括两种对象类型,其中一种是另一种的子类型,即{ x: 1 } | { x: 1, y: 2 }。
解决方法
- 使用第三个
extends 子句(如 Nurbol 的回答)
(...) extends false ? false : true;
- 使用
never 作为错误案例:
T extends unknown ? [U] extends [T] ? never : true : never;
- 在调用点反转
extends:
true extends IsUnion<T> ? Foo : Bar;
- 由于您可能需要一个条件类型才能在调用站点使用它,因此将其包装起来:
type IfUnion<T, Yes, No> = true extends IsUnion<T> ? Yes : No;
根据您的需要,您可以使用此类型进行许多其他变体。一种想法是将unknown 用于肯定情况。然后你可以做T & IsUnion<T>。或者您可以为此使用T 并将其称为AssertUnion,这样如果它不是联合类型,则整个类型将变为never。天空才是极限。
感谢 gitter 上的 @Gerrit0 和 @AnyhowStep 发现我的错误并就解决方法提供反馈。