首页 > 解决方案 > 具有特定编号的函数重载

问题描述

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

如果弹出计数设置为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([]);
  }
}

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

标签: typescriptoverloading

解决方案


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

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([]);
    }
}

游乐场链接

(由于默认为countis 1,因此我将其折叠到第一个重载签名中,count方法是在其类型为 时设置为可选1。)

请注意,尽管TypeScript 会假定您传递的任何非文字(或至少不是立即明显的常量)都表示回调需要一个数组,因为类型是,而不是。因此,如果您在调用中按字面意思指定(或作为立即明显的常量),则此重载仅适用于为回调提供项目(而不是数组) 。numbercountnumber11

例子:

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
);

游乐场链接

即使不是itemis的类型number[],在运行时它也会接收单个number,而不是数组,因为运行时代码只知道count参数是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[]在我之前的示例中给我们的情况变成了编译时错误:操场链接


推荐阅读