如何在Flow中注释具有多个可能的呼叫签名的函数?
在JavaScript中,通常有一个可以以多种方式调用的函数 – 例如less量位置参数或单个选项对象或二者的组合。
我一直在试图解决这个问题。
我尝试过的一种方法是将其余的参数注释为各种可能的元组的联合:
type Arguments = | [string] | [number] | [string, number] ; const foo = (...args: Arguments) => { let name: string; let age: number; // unpack args... if (args.length > 1) { name = args[0]; age = args[1]; } else if (typeof args[0] === 'string') { name = args[0]; age = 0; } else { name = 'someone'; age = args[1]; } console.log(`${name} is ${age}`); }; // any of these call signatures should be OK: foo('fred'); foo('fred', 30); foo(30);
以上片段是人为devise的, 我可以在这个例子中使用(...args: Array<string | number>)
,但是对于更复杂的签名(例如,涉及可以单独使用或与之前的参数一起的types化选项对象),能够有用定义一个精确的,有限的一组可能的呼号。
但是上面没有进行types检查。 你可以在tryflow中看到一堆令人困惑的错误。
我也尝试将函数本身作为单独的整个函数defs的联合来使用,但是这也不起作用 :
type FooFunction = | (string) => void | (number) => void | (string, number) => void ; const foo: FooFunction = (...args) => { let name: string; let age: number; // unpack args... if (args.length > 1) { name = args[0]; age = args[1]; } else if (typeof args[0] === 'string') { name = args[0]; age = 0; } else { name = 'someone'; age = args[1]; } console.log(`${name} is ${age}`); }; // any of these call signatures should be OK: foo('fred'); foo('fred', 30); foo(30);
我应该如何处理具有多个可能呼叫签名的types注释函数? (或者多重签名被认为是Flow中的一种反模式,我根本就不应该这么做 – 在这种情况下,与第三方库进行交互的推荐方法是什么?)
您看到的错误是代码中的错误和Flow中的错误的组合。
您的代码中存在错误
我们先来修复你的bug。 在第三个else语句中,将错误的值赋给
} else { name = 'someone'; age = args[1]; // <-- Should be index 0 }
将数组访问权限更改为正确的索引将删除两个错误。 我认为我们都可以同意这正是Flow的目的,在代码中发现错误。
缩小types
为了find问题的根本原因,我们可以在错误的地方更加明确,以便我们更容易地看到问题是什么:
if (args.length > 1) { const args_tuple: [string, number] = args; name = args_tuple[0]; age = args_tuple[1]; } else if (typeof args[0] === 'string') {
这与之前的效果是完全一样的,但是因为我们非常清楚args[0]
和args[1]
在这一点上应该是什么。 这给我们留下了一个单一的错误。
stream中的错误
剩下的错误是Flow中的一个错误: https : //github.com/facebook/flow/issues/3564
bug:元组types不与长度断言(.length> = 2和[] | [number] | [number,number]types)
如何input重载函数
在这种情况下,处理不同types的可变参数的stream程并不是很好。 variables更多的是像function sum(...args: Array<number>)
,其中所有types都是相同的,没有最大元数。
相反,你应该更清楚你的论点,就像这样:
const foo = (name: string | number, age?: number) => { let real_name: string = 'someone'; let real_age: number = 0; // unpack args... if (typeof name === 'number') { real_age = name; } else { real_name = name; real_age = age || 0; } console.log(`${real_name} is ${real_age}`); }; // any of these call signatures should be OK: foo('fred'); foo('fred', 30); foo(30);
这不会导致错误,我认为开发人员也更容易阅读。
一个更好的方法
在另一个答案中, 帕夫洛提供了另一个我比我自己更喜欢的解决scheme 。
type Foo = & ((string | number) => void) & ((string, number) => void) const foo: Foo = (name, age) => {...};
它以更简洁的方式解决了相同的问题,使您拥有更多的灵活性。 通过创build多个函数types的交集,您可以描述调用函数的各种不同方式,从而使Flow可以根据函数的调用方式来尝试每个函数。
在您给出的三种可能的训练中,我已经想出了如何使用单个选项对象来工作,但是因为您至less需要设置一个对象,所以您需要定义每个可能性。
喜欢这个:
type Arguments = {| +name?: string, +age?: number |} | {| +name: string, +age?: number |} | {| +name?: string, +age: number |}; const foo = (args: Arguments) => { let name: string = args.name ? args.name : 'someone'; let age: number = typeof args.age === 'number' && !isNaN(args.age) ? args.age : 0; console.log(`${name} is ${age}`); } // any of these call signatures are OK: foo({ name: 'fred' }); foo({ name: 'fred', age: 30 }); foo({ age: 30 }); // fails foo({});
您可以通过将它们与&
连接来定义多个函数签名:
type Foo = & ((string | number) => void) & ((string, number) => void)
尝试一下。