具有特定数字的函数重载

Function overload with specific number

想象一下我的要求的简化版本:我将一个项目数组传递给一个函数、一个回调以及我想从数组中弹出的项目数。回调将获得该数量的项目。

如果弹出计数设置为 1,我希望该回调仅接收该单个项目。如果它是来自 1 的任何其他内容,我希望它将一个数组传递给回调。

我不确定在 TypeScript 中是否可行。我一直在玩,但没有成功。这是我想出的(但行不通):

function pop<T>(items: T[], cb: (item: T) => void, count: 1): void;
function pop<T>(items: T[], cb: (item: T[]) => void, count: undefined): void;
function pop<T>(items: T[], cb: (item: T[]) => void, count: number): void;
function pop<T>(
  items: T[],
  cb: ((item: T) => void) | ((item: T[]) => void),
  count = 1,
): void {
  if (count === 1) {
    cb(items[0]);
  } else if (count > 1) {
    cb(items.slice(0, count));
  } else {
    cb([]);
  }
}

谁能告诉我这是否可能?或者我是否遗漏了什么?

这是可能的,你只需要放松实现签名(调用者看不到,他们只看到重载签名):

function pop<T>(items: T[], cb: (item: T) => void, count?: 1): void;
function pop<T>(items: T[], cb: (items: T[]) => void, count: number): void;
function pop<T>(
    items: T[],
    cb: (items: T | T[]) => void,
    count = 1,
): void {
    if (count === 1) {
        cb(items[0]);
    } else if (count > 1) {
        cb(items.slice(0, count));
    } else {
        cb([]);
    }
}

Playground link

(因为 count 的默认值是 1,我通过在 count 的类型是 1.)

请注意 TypeScript 会假定任何非文字(或至少不是立即明显不变的)number 你作为 count 传递意味着回调需要一个数组,因为类型是 number,而不是 1。因此,如果您在调用中指定 1 literally(或作为一个立即明显的常量),则此重载仅用于为项目(而不是数组)提供回调.

示例:

declare let items: number[];

pop(
    items,
    (item) => {
        console.log(item); // type is `number`
    },
    1
);

let count = 1;
pop(
    items,
    (item) => {
        console.log(item); // type is `number[]`, not `number`
    },
    count
);

Playground link

即使 item 的类型不是 number[],在运行时它也会收到一个 number,而不是一个数组,因为运行时代码只知道 [=14] =]参数是1,不是为什么1。正如 Jörg W Mittag 在评论中指出的那样,这是因为重载纯粹是 TypeScript 中的编译时/类型检查;在运行时实际发生的唯一部分是 JavaScript 实现,它不知道静态类型。 (这与 Java 这样的语言形成对比,在这些语言中,重载实际上是独立的函数,被调用的特定函数是在编译时确定的,而不是运行时。)

您可以通过几种方式解决这个问题:

  1. 改为定义两个单独的方法,popOnepop/popSome 或类似方法。
  2. 要求 const 是文字或编译时常量。

#1 是不言自明的,但是 captain-yossarian 向我们展示了如何通过 OnlyLiteral 通用类型来做 #2:

type OnlyLiteral<N> = N extends number ? number extends N ? never : N : never;

那么第二个重载签名是:

function pop<T>(items: T[], cb: (items: T[]) => void, count: OnlyLiteral<number>): void;

...在我之前的示例中给我们 number[] 的情况变成了编译时错误:playground link