Class 包装一个函数:获得正确的类型和访问参数的数量(在打字稿中)

Class wrapping a function: get correct typings and access to number of arguments (in typescript)

我写了一些 class,它包装了一个指定的函数,还为每个函数参数获取了一个验证模式列表。这个 class 有一个 call 函数,它应该采用与输入函数相同的参数,验证这些参数,调用输入函数,并包装结果,例如变成 rxjs/Observable。因此,构造函数应该检查函数指定的参数数量 method.length 是否与验证模式的数量相同。

原始 javascript 版本应如下所示:

class Wrapper {
    method;
    validationPatterns;

    constructor(method, validationPatterns) {
        if (method.length !== validationPatterns.length) {
            // throw error
        }
        this.method = method;
        this.validationPatterns = validationPatterns;
    }

    validate(...args) {
        this.validationPatterns.forEach(pattern => {
            // apply validation pattern
        });
    }

    call(...args) {
        // validate arguments
        this.validate(...args);
        // run method and wrap its result e.g. in an Observable
        const methodResult = this.method(...args);
        return Observable.of(methodResult);
    }
}

现在我在谷歌上搜索了很多,并尝试了不同的方法来将正确的类型应用到这个 class。但是我找不到一种方法,可以让我

  1. 访问构造函数中函数参数的数量(通过method.length)。所以不可能构造一个带有错误验证的 class 实例。
  2. 并且同时所有 class 函数的类型都正确。

我尝试了以下操作:

1。使用 <T extends Function>

优点:

缺点:

代码示例:

class Wrapper<F extends Function> {

    constructor(public method: F, public validationPatterns: any[]) {
        if (method.length !== validationPatterns.length) {
            // throw error
        }
    }

    validate(...args: any[]): void {
        // ...
    }

    call(...args: any[]): any {
        // ...
    }
}

2。使用单个函数参数。

优点:

缺点:

代码示例:

class Wrapper<T, U> {
    constructor(public method: (arg: T) => U, public validationPatterns: any[]) {
        // CANNOT check correct number of validation patterns
    }

    validate(args: T): void {
        // ...
    }

    call(args: T): Observable<U> {
        // ...
    }
}

3。超载

由于包装函数通常只有几个参数,在另一种方法中,我们可以尝试使用函数重载。但是,到目前为止,我只找到有关重载单个函数的信息。我想,我真正想要的是一个重载的 class,其中 call()validate() 的签名取决于构造函数重载。

我没有在 typescript 中重载的经验,但我猜具有独立重载函数的代码示例可能如下所示:

class Wrapper<F extends Function, T1, T2, T3, U> {

    constructor(method: (arg1: T1, arg2: T2, arg3: T3) => U, validationPatterns: any[]);
    constructor(method: (arg1: T1, arg2: T2) => U, validationPatterns: any[]);
    constructor(method: (arg1: T1) => U, validationPatterns: any[]);
    constructor(method: () => U, validationPatterns: any[]);
    constructor(public method: F, public validationPatterns: any[]) {
        // ...
    }

    validate(arg1: T1, arg2: T2, arg3: T3): void;
    validate(arg1: T1, arg2: T2): void;
    validate(arg1: T1): void;
    validate(): void;
    validate(...args: any[]): void {
        // ...
    }

    call(arg1: T1, arg2: T2, arg3: T3): Observable<U>;
    call(arg1: T1, arg2: T2): Observable<U>;
    call(arg1: T1): Observable<U>;
    call(): Observable<U>;
    call(...args: any[]): Observable<U> {
        // ...
    }
}

问题

谢谢!

你可以重载类但是有点复杂,你需要单独声明重载并为每个重载定义一个构造函数:

class WrapperImpl {

    constructor(public method: Function, public validationPatterns?: Function[]) {
        // ...
    }
    validate(...args: any[]): void {
        // ...
    }

    call(...args: any[]): Observable<any> {
        return new Observable<any>();
    }
}


type KeysOfUnion<T> = T extends any ? keyof T: never;
type IsValid<T, TResult> = KeysOfUnion<T> extends never ?  never : TResult;
const Wrapper: {
    new <U>  (m: ()=> U, ) :  {
        validate(): void
        call(): Observable<U>
    }
    new <T1, U>  (m: (a: T1)=> U, validationPatterns: [IsValid<T1, (a: T1)=>boolean>]) :  {
        validate(a: T1): void
        call(a: T1): Observable<U>
    }
    new <T2, T1, U>  (m: (a: T1, a2: T2)=> U, validationPatterns: [IsValid<T1, (a: T1)=>boolean>, IsValid<T2, (a: T2)=>boolean>]) :  {
        validate(a: T1, a2: T2): void
        call(a: T1, a2: T2): Observable<U>
    }
    // Add more as needed 
} = WrapperImpl

let w = new Wrapper(() => "");
w.call() // return Observable<string>

let w2 = new Wrapper((n: number) => "", [n=> true]);
w2.call(10) // return Observable<string>

我不会在实现中过多地使用泛型,无论如何您将需要使用非常通用的类型(即 Functionany),泛型在那里对您没有太大帮助。呼叫将被正确输入和检查,这是重要的部分。

我假设验证是函数,您也可以在这些函数上进行推理和类型安全。我不得不使用一些条件类型魔法让编译器 select 为每个参数数量正确重载,但它似乎运行良好。如果您有任何问题,请告诉我。

这种方法仍然至少有一个缺点,当与带有可选参数的函数一起使用时,因为可选参数变得必需:

function withOpt(n?: number) { return ""}
let w3 = new Wrapper(withOpt, [n=> true]);
w3.call(1) // 1 is required.