繁体   English   中英

基于输入参数的Typescript函数重载

[英]Typescript function overloading based on input arguments

我正在寻找一种重写或重载特定功能组的方法。

这是一个例子

const operationA = ({a, b, c}) => 'alpha';
const operationB = ({a, b }) => 'beta';
const operationC = ({a}) => 'gamma';

const operation = compose([operationA, operationB, operationC]);

operation({a}) // 'gamma'
operation({a, b}) // 'beta'
operation({a, b, c}) // 'alpha'

有没有办法在打字稿中执行此功能?

(下面我使用TypeScript 3.2)

如果我理解,您的问题的主要问题是在运行时选择正确的重载的难度。 将类型信息从TypeScript编译为JavaScript 并不是TypeScript的目标之一 (请参阅非目标5)。 TypeScript添加的类型系统在运行时会被完全擦除。 因此,如果要编写compose()来获取函数列表,则必须以某种方式能够在运行时检查这些函数以确定应在特定参数上调用哪个函数。 但是,该功能实际上在JavaScript中不存在。 好的,您可以使用一个函数的length属性来查看它期望有多少个参数,但是在您给出的示例中,每个函数仅使用一个参数。 因此,我们不能在此处使用该方法。

一种可能的前进方式是向每个函数添加属性 此属性将是一种方法,它使用一组可能的参数,如果这些参数对该函数有效,则返回true否则返回false 本质上,您是在手动添加该语言所缺少的必要检查功能。

如果这样做,我们可以使compose()接受此类“参数验证函数”的列表,如下所示:

type ArgValidatingFunction =
  ((...args: any[]) => any) & { validArgs(...args: any): boolean };

type UnionToIntersection<U> =
  (U extends any ? (k: U) => void : never) extends
  ((k: infer I) => void) ? I : never;

function compose<F extends ArgValidatingFunction[]>(...fn: F): UnionToIntersection<F[number]>;
function compose(...fn: ArgValidatingFunction[]): Function {
  return Object.assign(
    (...args: any[]) => (fn.find(f => f.validArgs(...args))!(...args)),
    { validArgs: (...args: any[]) => fn.some(f => f.validArgs(...args)) }
  );
}

compose的类型签名接受ArgValidatingFunction参数列表,并返回其元素交集 TypeScript将重载表示为签名的顺序相关交集。 我不能100%保证编译器会产生与传递的函数相同的重载顺序,但似乎可以在我的测试中使用。

compose实现利用ArgValidatingFunctionvalidArgs方法,并对传入的函数执行find()以选择适当的函数。 我还在返回的函数上实现了validArgs()方法,以便compose()的返回值也是ArgValidatingFunction (这很不错,因为类型签名声称是)。

现在我们可以尝试使用它,但是它并不简单……我们必须添加这些方法:

const operationA = ({ a, b, c }: { a: any, b: any, c: any }): 'alpha' => 'alpha';
operationA.validArgs = (...args: any[]) => 
  (args.length === 1) && ('a' in args[0]) && ('b' in args[0]) && ('c' in args[0]);

const operationB = ({ a, b }: { a: any, b: any }): 'beta' => 'beta';
operationB.validArgs = (...args: any[]) => 
  (args.length === 1) && ('a' in args[0]) && ('b' in args[0]);

const operationC = ({ a }: { a: any }): 'gamma' => 'gamma';
operationC.validArgs = (...args: any[]) => 
  (args.length === 1) && ('a' in args[0]);

开始了:

const operation = compose(operationA, operationB, operationC);

const beta = operation({ a: 3, b: 3 }); // "beta" at compile time;
console.log(beta); // "beta" at runtime

看起来它在编译时和运行时都可以工作。


所以这是要走的路。 这并不容易,也不是很漂亮,但是也许适用于您(或某人)的用例。 希望能有所帮助。 祝好运!

您可能已经评估过的一种方法是,将Interface用作主要operation方法的输入,然后根据输入分配正确的子方法。

所以像这样:

interface OperationArgs {
 a: string;
 b?: string;
 c?: string;
}

因此,第一个值是必需的,其他两个是可选的。

在您的operation方法内,您可以执行以下operation

public operation(inp: OperationArgs) {
  if (inp.c) {
    return this.operationC(inp);
  }
  if (inp.b) {
    return this.operationB(inp);
  }
  return this.operationA(inp);
}

另一种方法是使用代理,但是在JS中尚未完全支持它们(缺少浏览器)。 您可以创建一个返回Proxy实例的类,并使用处理程序的get方法捕获operation方法。 根据给定的道具,您实际上将在实例上调用正确的方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM