AnyOf
挑战
在类型系统实现类似于 Python 的 any
函数。这个类型接受一个数组,如果数组中的任
一元素为真,则类型返回 true
。如果数组为空,则返回 false
。例如:
type Sample1 = AnyOf<[1, "", false, [], {}]>; // expected to be true
type Sample2 = AnyOf<[0, "", false, [], {}]>; // expected to be false
解答
看到这个挑战后,我第一个想法是使用 distributive conditional types.
我们可以使用 T[number]
语法来获取一个元组中所有元素组成的联合。有了这元素组成
的联合后,我们对它进行迭代,得出一个 false
或 true
联合。如果所有元素都返回
false
,我们会得出 false
的类型字面量。但是,即使只有一个 true
-y 元素,结果
也就产生了 true
的类型字面量。因此,false
和 true
类型字面量构成了我们的联
合类型,其产出则是 boolean
。通过检查联合类型的产出与 false
类型字面量之间的
继承关系,以判断是否存在 true
元素。
但是,这个实现被证明是非常古怪的。我不喜欢它,看一看:
type AnyOf<T extends readonly any[], I = T[number]> = (
I extends any ? (I extends Falsy ? false : true) : never
) extends false
? false
: true;
所以我开始思考,我们是否可以让它更易于维护呢?事实证明我们可以。让我们回忆一下从 元组类型中推断与可变元组类型相结合的情况。记得我们在解 Last 挑战或 Pop 之类的问题时使用过这些。
我们从推断元组中的单一元素开始,并推断族中其余的元素:
type AnyOf<T extends readonly any[]> = T extends [infer H, ...infer T]
? never
: never;
我们如何检查推断的元素 H
是否为 false-y?首先,我们可以构造一个表示 false-y 的
类型。我们称它为 Falsy
:
type Falsy = 0 | "" | false | [] | { [P in any]: never };
有了一个表示 falsy-y 值的类型,我们可以只使用条件类型来检查 H
是否从该类型中扩
展:
type AnyOf<T extends readonly any[]> = T extends [infer H, ...infer T]
? H extends Falsy
? never
: never
: never;
如果元素是 false-y,我们该怎么做?这意味着,我们仍在试图检查是否至少有一个 true-y 元素。所以我们可以继续递归:
type AnyOf<T extends readonly any[]> = T extends [infer H, ...infer T]
? H extends Falsy
? AnyOf<T>
: never
: never;
最后,一旦我们看到有元素不是 false-y,就意味着它是 true-y。由于我们已经知道存在
true-y 元素,因此继续递归就没有意思了。所以我们只需要通过返回 true
类型字面量
来退出递归:
type AnyOf<T extends readonly any[]> = T extends [infer H, ...infer T]
? H extends Falsy
? AnyOf<T>
: true
: never;
最后的状态是当我们有一个空元组时。在这种情况下,我们的推断将不起作用,这意味着绝
对没有 true-y 元素。在这种情况下,我们可以返回 false
。
type AnyOf<T extends readonly any[]> = T extends [infer H, ...infer T]
? H extends Falsy
? AnyOf<T>
: true
: false;
这就是我们在类型系统中实现的 AnyOf
函数的方式。以下是整个实现,供参考:
type Falsy = 0 | "" | false | [] | { [P in any]: never };
type AnyOf<T extends readonly any[]> = T extends [infer H, ...infer T]
? H extends Falsy
? AnyOf<T>
: true
: false;
评论