我的倾向是让union
and intersection
generic in the 元组类型 T
对应每个类型validators
is guarding。例如,如果您有验证器vx
类型的Validator<X>
, vy
类型的Validator<Y>
, and vz
类型的Validator<Z>
,然后当你打电话时union([vx, vy, vz])
我们想要T
to be [X, Y, Z]
.
那么我们可以表示validators
输入为简单的元组上的映射类型, like {[I in keyof T]: Validator<T[I]>}
。为了暗示我们想要T
是一个元组而不是无序的数组类型,我们可以把它写成可变元组类型, [...{ I in keyof T]: Validator<T[I]> }]
。因为映射类型是同态 (see “同态映射类型”是什么意思?),编译器能够推断T
来自该映射类型的值。
这意味着我们需要担心的是返回类型union
and intersection
.
For union
,问题是:“给定一个元组类型T
,我们如何编写一个类型union它的所有元素类型”?这相对容易编写;我们需要做的就是索引到类型与number
,因为元组类型已经有一个number
索引签名与元素类型的联合。 (希望这是有道理的;如果你有一个价值a
类型的[string, number, boolean]
并用 a 对其进行索引number
i
这不是越界,那么类型a[i]
将string | number | boolean
):
const union = <T extends any[]>(
validators: [...{ [I in keyof T]: Validator<T[I]> }]
): Validator<T[number]> => {
return (doc: any): doc is T[number] => {
for (const validator of validators) {
if (validator(doc)) return true;
}
return false;
}
}
让我们测试一下:
declare const oneValidator: Validator<FooOne>;
declare const twoValidator: Validator<FooTwo>;
declare const threeValidator: Validator<FooThree>;
const uv1 = union([oneValidator]);
// const uv1: Validator<FooOne>
const uv12 = union([oneValidator, twoValidator]);
// const uv12: Validator<FooOne | FooTwo>
const uv123 = union([oneValidator, twoValidator, threeValidator]);
// const uv123: Validator<FooOne | FooTwo | FooThree>
看起来不错。
For intersection
,问题是:“给定一个元组类型T
,我们如何编写一个类型路口它的所有元素类型”?这更复杂。没有简单的方法可以得到这个......索引访问类型对应于您读取属性时获得的内容,而不是写入属性。
为了将元组转换为其所有元素类型的交集,我们需要编写自己的实用程序类型,将元组映射到包含元素的版本逆变键入位置(参见TypeScript 中方差、协方差、逆变和双方差的区别)然后使用条件类型推断通过infer推断出这些的单一类型,这将成为交集,如记录的那样。
它看起来像这样:
type TupleToIntersection<T extends any[]> = {
[I in keyof T]: (x: T[I]) => void
}[number] extends (x: infer R) => void ? R : never;
您可以验证其是否按预期工作:
type Test = TupleToIntersection<[{ a: string }, { b: number }, { c: boolean }]>
// type Test = { a: string; } & { b: number; } & { c: boolean; }
因此intersection
好像
const intersection = <T extends any[]>(
validators: [...{ [I in keyof T]: Validator<T[I]> }]
): Validator<TupleToIntersection<T>> => {
return ((doc: any): doc is TupleToIntersection<T> => {
for (const validator of validators) {
if (!validator(doc)) return false;
}
return true;
});
}
让我们测试一下:
const iv1 = intersection([oneValidator]);
// const iv1: Validator<FooOne>
const iv12 = intersection([oneValidator, twoValidator]);
// const iv12: Validator<FooOne & FooTwo>
const iv123 = intersection([oneValidator, twoValidator, threeValidator]);
// const iv123: Validator<FooOne & FooTwo & FooThree>
看起来也不错。
Playground 代码链接