简体   繁体   English

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

[英]Typescript function overloading based on input arguments

I'm looking for a way to override or overload a specific group of functions. 我正在寻找一种重写或重载特定功能组的方法。

Here's an example 这是一个例子

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'

Is there a way to do this functionality within typescript? 有没有办法在打字稿中执行此功能?

(In the following I'm using TypeScript 3.2) (下面我使用TypeScript 3.2)

The main issue with your question, if I understand it, is the difficulty of choosing the right overload at runtime. 如果我理解,您的问题的主要问题是在运行时选择正确的重载的难度。 It is not one of TypeScript's goals (see Non-Goal #5) to compile type information from TypeScript into JavaScript. 将类型信息从TypeScript编译为JavaScript 并不是TypeScript的目标之一 (请参阅非目标5)。 The type system added by TypeScript is completely erased at runtime. TypeScript添加的类型系统在运行时会被完全擦除。 So, if you want to write compose() to take a list of functions, somehow you have to be able to inspect those functions at runtime to determine which one should be called on a particular argument. 因此,如果要编写compose()来获取函数列表,则必须以某种方式能够在运行时检查这些函数以确定应在特定参数上调用哪个函数。 That functionality really doesn't exist in JavaScript, though. 但是,该功能实际上在JavaScript中不存在。 Well, you can kind of use the length property of a function to see how many arguments it expects, but in the examples you gave, each function takes exactly one argument. 好的,您可以使用一个函数的length属性来查看它期望有多少个参数,但是在您给出的示例中,每个函数仅使用一个参数。 So we can't use that approach here. 因此,我们不能在此处使用该方法。

One possible way forward is to add a property to each function. 一种可能的前进方式是向每个函数添加属性 This property would be a method that takes a potential set of arguments and returns true if those arguments are valid for the function, and false if they are not. 此属性将是一种方法,它使用一组可能的参数,如果这些参数对该函数有效,则返回true否则返回false Essentially you're manually adding the necessary inspection ability that is missing from the language. 本质上,您是在手动添加该语言所缺少的必要检查功能。

If we do this, we can make compose() accept a list of such "argument validating functions", like this: 如果这样做,我们可以使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)) }
  );
}

The type signature for compose accepts a list of ArgValidatingFunction arguments and returns the intersection of its elements . compose的类型签名接受ArgValidatingFunction参数列表,并返回其元素交集 TypeScript represents overloads as an order-dependent intersection of signatures. TypeScript将重载表示为签名的顺序相关交集。 I can't 100% guarantee that the compiler will produce the same overload order as the functions passed in, but it seems to work in my testing. 我不能100%保证编译器会产生与传递的函数相同的重载顺序,但似乎可以在我的测试中使用。

The implementation of compose makes use of the ArgValidatingFunction 's validArgs method, and does a find() on the passed-in functions to choose the proper function. compose实现利用ArgValidatingFunctionvalidArgs方法,并对传入的函数执行find()以选择适当的函数。 I also implement a validArgs() method on the returned function so that the return value of compose() is also an ArgValidatingFunction (which is good because the type signature claims that it is). 我还在返回的函数上实现了validArgs()方法,以便compose()的返回值也是ArgValidatingFunction (这很不错,因为类型签名声称是)。

Now we can try to use it, but it's not trivial... we have to add those methods: 现在我们可以尝试使用它,但是它并不简单……我们必须添加这些方法:

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]);

Here we go: 开始了:

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

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

Looks like it works both at compile time and runtime. 看起来它在编译时和运行时都可以工作。


So that's one way to go. 所以这是要走的路。 It's not easy or pretty, but maybe it works for your (or someone's) use case. 这并不容易,也不是很漂亮,但是也许适用于您(或某人)的用例。 Hope that helps. 希望能有所帮助。 Good luck! 祝好运!

An approach, which maybe you have already evaluated is to use an Interface as input of your main operation method, and then dispatch the right sub methods depending on the input. 您可能已经评估过的一种方法是,将Interface用作主要operation方法的输入,然后根据输入分配正确的子方法。

So something like: 所以像这样:

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

So the first value is mandatory, the other two are optionals. 因此,第一个值是必需的,其他两个是可选的。

Inside your operation method you can do something like: 在您的operation方法内,您可以执行以下operation

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

Another approach is to use Proxy but they are not fully supported yet in JS (explorer is missing). 另一种方法是使用代理,但是在JS中尚未完全支持它们(缺少浏览器)。 You could create a class which returns a Proxy instance, and trap the operation methods using the get method of the handler. 您可以创建一个返回Proxy实例的类,并使用处理程序的get方法捕获operation方法。 Depending on the given props, you will actually call the right method on the instance. 根据给定的道具,您实际上将在实例上调用正确的方法。

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

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