编译器查看了您的return 语句并推断 range 的返回类型是union type ((end: number) => number[]) | number[]。因此,当您调用range() 时,编译器只知道它返回另一个函数 或 一个数字数组。 如何你调用range()和这两种类型中的哪个返回值实际上是:
const r33 = range(3, 3); // [3]
// const r33: ((end: number) => number[]) | number[]
var start3 = range(3);
// var start3: ((end: number) => number[]) | number[]
看到了吗? r33 和 start3 都被视为同一类型,可能是一个数组,但 可能是一个函数。它看不出start3(3)和r33(3)之间的区别。
所以如果你调用start3(3),编译器会警告你它不能确定你没有像调用函数一样调用数组。除非您在调用它之前明确检查 start3:
var start3 = range(3);
if (typeof start3 !== "function") throw new Error();
start3(3); // okay
start3(8); // okay
start3(0); // okay
或者除非你只是 assert 它是一个函数:
(start4 as Extract<typeof start4, Function>)(6); // okay
这两者都不是你想做的。
因此编译器无法真正推断输入数量和输出类型之间的关系。但是有办法告诉编译器这个信息,这样调用者就可以更轻松了。
也许最直接的方法是将range() 变成overloaded function。您首先提供一组调用签名声明,对应于调用者可以预期的不同的不同输入-输出关系:
// call signatures
function range(start: number, end: number): number[];
function range(start: number): (end: number) => number[];
然后你像以前一样编写实现:
function range(start: number, end?: number) {
// same impl here
}
这编译得很好,尽管编译器并没有非常彻底地检查它。例如,如果我在这些调用签名中混合了两种返回类型(将number[] 放在第二个上,将(end: number) => number[] 放在第一个上),编译器就不会捕捉到这样的错误。重载函数实现的检查比普通函数实现更宽松,所以你需要小心。
不管怎样,现在调用者可以调用range 并且有更合理的行为:
const r33 = range(3, 3); // [3]
// const r33: number[]
var start3 = range(3);
// var start3: (end: number) => number[]
现在编译器知道r33 是一个数组,start3 是一个函数。之后的调用将按预期工作。
另一种方法是使range() 成为generic 函数,其返回类型为conditional type,这取决于函数的调用方式:
function range<T extends [end: number] | []>(start: number, ...[end]: T):
T extends [] ? (end: number) => number[] : number[] {
start = Number(start) || 0;
if (end === undefined) {
return function getEnd(end: number) {
return getRange(start, end);
} as any;
} else {
end = Number(end) || 0;
return getRange(start, end) as any;
}
// same getRange impl here
}
这里我们说的是,在start 参数之后,您可以期望rest of the arguments T 可以分配给与end 参数对应的单个元素tuple,或者是一个空的元组对应没有 end 参数。然后T extends [] ? (end: number) => number[] : number[] 的返回值根据T 计算为函数类型或数组类型。
编译器仍然无法遵循函数内部的逻辑来验证实际返回是否符合此泛型条件类型(有关更多信息,请参阅microsoft/TypeScript#33912),但与重载不同,编译器会抱怨这一点。所以现在这意味着你需要类型断言之类的东西来使返回语句在没有警告的情况下编译(参见我添加的as any),因此这个解决方案与重载一样不安全。
无论哪种方式,您都需要注意调用签名所表达的输入/输出关系实际上是正确实现的。
Playground link to code