如何检查调用签名之间的类型兼容性?

Dev*_*per 4 typescript

考虑以下片段:

type Add = (a: number | string, b: number | string) => number | string;

function hof(cb: Add) {}

const addNum = (a: number, b: number): number => a + b;
const addStr = (a: string, b: string): string => a + b;

// @ts-expect-error
hof(addNum);
// @ts-expect-error
hof(addStr);
Run Code Online (Sandbox Code Playgroud)

为什么我们不能将addNumaddStr函数传递给hof,IMO 他们的调用签名应该与hof预期兼容,但实际上并非如此。

如果它们的类型不兼容,那么为什么以下代码片段中的重载签名不会抱怨?

function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: number | string, b: number | string): number | string {
  return 1;
}
Run Code Online (Sandbox Code Playgroud)

Ale*_*yne 6

您假设协变类型,但函数参数是逆变的。(有关这方面的一些深度,请参阅这篇文章。)


将您的示例稍微更改一下,您就会开始看到问题:

type Add = (a: number | string, b: number | string) => number | string;

function hof(cb: Add) {
  return cb
}

const addNum = (a: number, b: number): number => Math.round(a) + Math.round(b);
const addStr = (a: string, b: string): string => `${a.toLowerCase()}${b.toLowerCase}`;

hof(addNum)('a', 'b'); // should error because addNum can't take strings
hof(addStr)(123, 456); // should error because addStr can't take numbers
Run Code Online (Sandbox Code Playgroud)

这里hof返回你传递给它的回调函数。所以如果你通过它addNum那么字符串就会崩溃。如果你通过了它,addStr那么数字就会崩溃。

因此,对于可分配给函数类型的函数,它必须采用所有参数类型的超集而不是子集。

操场


但是,如果您使用hof泛型,则可以从参数创建函数类型。例如:

type Add<T extends number | string> = (a: T, b: T) => T;

function hof<T extends number | string>(cb: Add<T>): Add<T> {
  return cb
}

const addNum = (a: number, b: number): number => Math.round(a) + Math.round(b);
const addStr = (a: string, b: string): string => `${a.toLowerCase()}${b.toLowerCase}`;

hof(addNum)(123, 456); // fine
hof(addStr)('a', 'B'); // fine

hof(addNum)('a', 'b'); // should error because addNum can't take strings
hof(addStr)(123, 456); // should error because addStr can't take numbers
Run Code Online (Sandbox Code Playgroud)

操场