Typescript 泛型函数,其中参数组成返回类型

2023-12-12

我有一组带有签名的类型保护函数:

type Validator<T> = (doc: any) => doc is T;

我希望能够编写这些验证器。例如:

export const union = <T>(validators: Validator<>[]): ValidatorFn<T> => {
  return (doc: any): doc is T => {
     for (const validator of validators) {
       if (validator(doc)) return true;
     }
     return false;
  }
}

export const intersection = <T>(validators: Validator<>[]): ValidatorFn<T> => {
  return (doc: any): doc is T => {
     for (const validator of validators) {
       if (!validator(doc)) return false;
     }
     return true;
  }
}

但是,我真的不知道如何输入validators参数使得其中的任何内容要么是“in”T,要么是“sums up”到T。例如,以下内容应该有效:

interface FooOne {
  a: string;
}

interface FooTwo {
  b: string;
}

interface FooThree {
  c: string
}

type Bar = FooOne | FooTwo
type Baz = FooOne & FooTwo

const oneValidator = validator<FooOne>()
const twoValidator = validator<FooTwo>()
const threeValidator = validator<FooThree>()


const barValidator = union<Bar>([oneValidator, twoValidator])  // should succeed
const barValidator = union<Bar>([oneValidator])  // should succeed because FooOne is sufficient to validate Bar
const barValidator = union<Bar>([oneValidator, twoValidator, threeValidator])  // should fail because FooThree is not in Bar

const bazValidator = intersection<Baz>([oneValidator, twoValidator]) // should succeed
const bazValidator = intersection<Baz>([oneValidator]) // should fail because validating FooOne is insufficient to validate Baz
const bazValidator = intersection<Baz>([oneValidator, twoValidator, threeValidator]) // should fail because FooThree is not in Baz

我如何设置类型,以便打字稿编译器足够智能来评估这些组合?


我的倾向是让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 代码链接

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Typescript 泛型函数,其中参数组成返回类型 的相关文章

随机推荐