为什么TypeScript在函数引用上选择不同于lambda扭曲调用的重载



为什么TS选择最后一个重载变量,而不是最后一种情况下的中间f(x: string): string,因为它给出了一种类型的(string | null)[]而不是string[]

(TypeScript版本4.4.4(

function f(x: null): null;
function f(x: string): string;
function f(x: string | null): string | null;
function f(x: string | null): string | null {
if (x === null) return x;
return `${x}`;
}
f(null); // null
f('foo'); // string
f(Math.random() > 0.5 ? 'bar' : null); // string | null
['a', 'b', 'c'].map(x => f(x)); // string[]
['a', 'b', 'c'].map(f); // (string | null)[]

这是TypeScript中的一个设计限制。有关权威答案,请参阅microsoft/TypeScript#35501。

只有在不需要首先进行任何类型推理的情况下,才能保证重载解析正确进行。如果您使用已知类型的参数直接调用重载函数,那么它将按预期工作。但是,编译器需要推断类型的重载函数签名的任何操作都将使用一些启发式方法,从重载列表中急切地选择或合成单个调用签名,而不考虑如果直接调用哪个签名实际上是合适的。这往往是最后一个呼叫签名(或者有时可能是第一个?根据这条评论(。

在您的情况下,数组map()方法需要推断与回调函数的返回类型相对应的泛型类型参数U。编译器急切地选择最后一个调用签名,看到它的返回类型是string | null,然后继续:

['a', 'b', 'c'].map(f); // (string | null)[]

这个问题也发生在条件类型推理中,包括流行的Parameters<T>ReturnType<T>实用程序类型:

type FParam = Parameters<typeof f>[0]; // type FParam = string | null
type FReturn = ReturnType<typeof f>; // type FReturn = string | null

例如,如果您想在不将f封装在x => f(x)中的情况下调用map(),则可以通过手动指定泛型类型参数来回避此问题:

['a', 'b', 'c'].map<string>(f); // string[]

现在不需要类型推理了。。。编译器知道Ustring,因此需要将f解释为(value: string) => string类型的值。在没有推理的情况下,重载解析会按预期进行,因此选择了第二个重载,不会发生编译器错误。当然,由于您将U指定为string,映射操作的返回类型为string[]

游乐场链接到代码

最新更新