Skip to content

IsUnion

约 825 字大约 3 分钟

2022-12-01

题目

Github: IsUnion

实现一个类型 IsUnion,它接受一个输入类型 T 并返回 T 是否解析为联合类型。

type case1 = IsUnion<string> // false
type case2 = IsUnion<string | number> // true
type case3 = IsUnion<[string | number]> // false

解题思路

起初看到这道题目时,我有些无从下手,在看了 #1140 的回答后才恍然大悟。

首先,我们需要思考 联合类型 及其含义。

当我们指定一个普通类型时,比如 string,它将永远是字符串。当我们指定一个联合类型时,比如 string | number , 它可以是 字符串,也可以是 数值 。

普通类型并不能表示一组值,但是 联合类型 可以。因此,在 普通类型 上进行分布式迭代是没有意义的,但对于 联合类型 来说是有意义的。

这就是我们如何检测是否是 联合类型 的关键点。但对 类型 T (普通类型)进行分布式迭代时,它不会产生变化, 但如果 类型 T 是 联合类型,它会产生比较大的变化。

当我们构造一个 T extends T ? ConditionTrue<T> : never 时,比如 T 为 联合类型 string | number:

我们继续构造 ConditionTrue<T> = string | number extends T ? true : false

看到了吧!如果 T 是联合类型,那么它的每个元素 subT 必然不能满足 T extends subT

基于以上,我们可以开始实现 IsUnion 类型。

首先保存一个类型 T 的副本,以方便后续的使用:

type IsUnion<T, C extends T = T> = never

通过应用条件类型,我们得到了分布式语义。在条件类型的 true 分支中,我们将获取联合 类型中的每一项。

type IsUnion<T, C extends T = T> = T extends T ? true : never

接下来,将每一项与原来的输入类型T进行比较:

type IsUnion<T, C extends T = T> = T extends T
  ? C extends T
    ? false : true
  : never

当然,我们还需要完善一些边界情况。

答案

type IsUnion<T, C extends T = T> = (T extends T
  ? C extends T
    ? true
    : unknown
  : never) extends true
  ? false
  : true

验证

type 
cases
= [
Expect
<
Equal
<
IsUnion
<string>, false>>,
Expect
<
Equal
<
IsUnion
<string | number>, true>>,
Expect
<
Equal
<
IsUnion
<'a' | 'b' | 'c' | 'd'>, true>>,
Expect
<
Equal
<
IsUnion
<undefined | null | void | ''>, true>>,
Expect
<
Equal
<
IsUnion
<{
a
: string } | {
a
: number }>, true>>,
Expect
<
Equal
<
IsUnion
<{
a
: string | number }>, false>>,
Expect
<
Equal
<
IsUnion
<[string | number]>, false>>,
// Cases where T resolves to a non-union type.
Expect
<
Equal
<
IsUnion
<string | never>, false>>,
Expect
<
Equal
<
IsUnion
<string | unknown>, false>>,
Expect
<
Equal
<
IsUnion
<string | any>, false>>,
Expect
<
Equal
<
IsUnion
<string | 'a'>, false>>,
Expect
<
Equal
<
IsUnion
<never>, false>>,
]

参考