约束TypeScript中的函数参数类型以创建返回目标函数的管道

wkyowqbh  于 2022-12-30  发布在  TypeScript
关注(0)|答案(1)|浏览(103)

我正在构建一个函数管道,它创建一系列检查/保护,然后接受一个函数并返回一个函数,该函数要么提前返回,要么调用接受的函数。

// A generic function type. The goal is to have the pipeline work with any type of function
interface GoalFunctionType { (): string }

// Pipeline instantiation and usage `checkSomething` and `provideName` are example functions in the pipeline, they are not implemented here.:
const p = new FunctionPipeline<GoalFunctionType>()
const passedInFunction = ({ name }: { name: string }) => "Hello " + name
const goalFunction: GoalFunctionType = p.checkSomething().provideName().finally(passedInFunction);

finally中,流水线检查会触发提前返回,它们可以选择为传入finally的函数创建额外的参数,就像上面的provideName一样(但下面的实现还没有达到这个程度)。
我被finally函数的类型检查器卡住了。我希望类型检查器确保传入的函数

  • 具有与GoalFunctionType相同的返回类型
  • 接受与GoalFunctionType相同的参数
  • 接受其他管道生成的参数作为第一个参数中的命名参数(此处未实现)

下面是一个最小实现(CodeSandbox),编译时没有出现错误/警告:

class FunctionPipeline<FunctionType extends (...args: any[]) => any> {
  finally(
    fn: (...args: Parameters<FunctionType>) => ReturnType<FunctionType>
  ): FunctionType {
    return (...args) => {
      return fn(...args);
    };
  }
}

interface LoaderFunction {
  ({ name }: { name: string }): string;
}

const goalFunction = new FunctionPipeline<LoaderFunction>().finally(
  ({ name }) => {
    const result = `Hello ${name}`;
    console.log(result);
    return result;
  }
);

const app = document.getElementById("app");
if (app) app.innerHTML = goalFunction({ name: "World" });

为了实现流水线生成的参数,finally函数应该更像这样,并希望具有特定的类型:

fn: (pipelineArgs: GeneratedArgsType, ...args: Parameters<FunctionType>) => ReturnType<FunctionType>
  ): FunctionType {
  return (...args) => {
    // example: this.generatedArgs == { name: "Nathan" };
    return fn(this.generatedArgs, ...args);
  };
}

functionPipeline.finally方法有两个编译器错误。
第一次返回时出错:

Type '(...args: any[]) => ReturnType<FunctionType>' is not assignable to type 'FunctionType'.
  '(...args: any[]) => ReturnType<FunctionType>' is assignable to the constraint of type 'FunctionType', but 'FunctionType' could be instantiated with a different subtype of constraint '(...args: any[]) => any'.

第二次返回时出错:

(parameter) args: any[]
Argument of type 'any[]' is not assignable to parameter of type 'Parameters<FunctionType>'.

你能帮我找出正确的类型来实现我的目标吗?这是minimal example in CodeSandbox。如果你想看到更多的代码,check out this longer example that provides more application context and usage in the Remix framework

xvw2m8pv

xvw2m8pv1#

您的版本正在生成错误,因为无法保证在调用finally()FunctionPipeline<F>可以生成F类型的generic值。F可以是任何函数类型的任何子类型,包括具有额外属性的函数:

function foo(x: string) {
    return x.length;
}
foo.strProp = "hey";
const gf = new FunctionPipeline<typeof foo>;
gf.finally(foo).strProp.toUpperCase() // no compiler error, but:
// 💥 RUNTIME ERROR 💥 gf.finally(...).prop is undefined

这会在运行时崩溃,因为typeof foo已知具有strProp属性,而您的finally()实现没有。
无论如何,您并不真正关心函数类型F;如果你在调用new FunctionPipeline<F>()时不依赖于手动指定类型参数,那么我建议你应该重构以使用你关心的两种类型:

class FunctionPipeline<A extends any[], R> {
    finally(
        fn: (...args: A) => R
    ): (...args: A) => R {
        return (...args) => {
            return fn(...args);
        };
    }
}

const goalFunction = new FunctionPipeline<[{ name: string }], string>().finally(
    ({ name }) => {
        const result = `Hello ${name}`;
        console.log(result);
        return result;
    }
);

这更直接,因为,例如,finally()产生(...args: A) => R类型的函数。
但是,假设这是不可接受的,另一种方法是从F计算AR,然后完全丢弃F

class FunctionPipeline<F extends (...args: any[]) => any> {
    finally(
        fn: (...args: Parameters<F>) => ReturnType<F>
    ): (...args: Parameters<F>) => ReturnType<F> {
        return (...args) => {
            return fn(...args);
        };
    }
}

因此FunctionPipeline<F>finally()方法返回(...args: Parameters<F>) => ReturnType<F>类型的值,并且不声明返回F类型的值,这就解决了我在foo中所展示的问题:

function foo(x: string) {
    return x.length;
}
foo.strProp = "hey";

const gf = new FunctionPipeline<typeof foo>;
gf.finally(foo).strProp // compiler error! 
// Property 'strProp' does not exist on type '(x: string) => number'

现在编译器认为gf.finally(foo)没有strProp属性,你的示例代码也可以工作:

const goalFunction = new FunctionPipeline<LoaderFunction>().finally(
    ({ name }) => {
        const result = `Hello ${name}`;
        console.log(result);
        return result;
    }
);

Playground代码链接

相关问题