【问题标题】:Typescript function overloading based on input arguments基于输入参数的打字稿函数重载
【发布时间】:2019-01-03 19:17:33
【问题描述】:

我正在寻找一种方法来覆盖或重载一组特定的函数。

这是一个例子

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 中实现这个功能?

【问题讨论】:

  • 您是在问如何在 TypeScript 中编写 compose() 函数吗?由于 TypeScript 编译为 JavaScript,因此您需要能够在 JavaScript 中编写无类型的compose()。您将如何建议在运行时确定使用哪个重载?
  • 另外,虽然object property shorthandobject destructuring assignment 很简洁,但很难理解这个问题发生了什么。如所写,代码无法编译(即使给了我们compose())。 abc 是否应该是范围内的变量?在operationA()等中,是a等的类型,应该是any吗?

标签: javascript typescript overloading


【解决方案1】:

(下面我使用的是 TypeScript 3.2)

如果我理解的话,您的问题的主要问题是难以在运行时选择正确的重载。将类型信息从 TypeScript 编译成 JavaScript 是 not one of TypeScript's goals(参见非目标 #5)。 TypeScript 添加的类型系统在运行时被完全擦除。因此,如果您想编写compose() 来获取函数列表,则必须以某种方式在运行时检查这些函数以确定应该在特定参数上调用哪个函数。但是,该功能在 JavaScript 中确实不存在。好吧,您可以使用函数的 length 属性来查看它需要多少 许多 个参数,但是在您给出的示例中,每个函数都只接受一个参数。所以我们不能在这里使用这种方法。

一种可能的前进方式是向每个函数添加property。这个属性将是一个方法,它接受一组潜在的参数,如果这些参数对函数有效,则返回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 参数列表并返回intersection of its elements。 TypeScript 将overloads 表示为签名的顺序相关交集。我不能 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

看起来它在编译时和运行时都有效。


所以这是一种方法。这并不容易或漂亮,但也许它适用于您(或某人)的用例。希望有帮助。祝你好运!

【讨论】:

    【解决方案2】:

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

    比如:

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

    所以第一个值是强制性的,其他两个是可选的。

    在您的 operation 方法中,您可以执行以下操作:

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

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

    【讨论】:

      猜你喜欢
      • 2019-11-15
      • 2020-05-20
      • 1970-01-01
      • 2021-01-23
      • 2020-08-30
      • 1970-01-01
      • 2017-01-04
      • 2019-07-04
      相关资源
      最近更新 更多