【问题标题】:Function overload with specific number具有特定编号的函数重载
【发布时间】:2021-11-01 09:35:49
【问题描述】:

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

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

谁能告诉我这是否可能?还是我错过了什么?

【问题讨论】:

  • 重载是针对调用者的;在实际实现中cb 不是有条件类型的,它始终是((item: T) =&gt; void) | ((item: T[]) =&gt; void)。请注意,您可以将第二个和第三个重载压缩为 count?: number

标签: typescript overloading


【解决方案1】:

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

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 时将其设置为可选,从而将其折叠到第一个重载签名中。) p>

请注意,TypeScript 将假定任何非文字(或至少不是立即明显恒定的)number 您传递为 count 意味着callback 需要一个数组,因为类型是 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,而不是一个数组,因为运行时代码只知道count参数是1,而不是为什么是1。正如 Jörg W Mittag 在 cmets 中指出的那样,这是因为重载纯粹是 TypeScript 中的编译时/类型检查。在运行时实际发生的唯一部分是 JavaScript 实现,它不知道静态类型。 (这与 Java 等语言形成鲜明对比,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

【讨论】:

  • 可以禁止non-literal numbers。考虑这个例子:type OnlyLiteral&lt;N&gt; = N extends number ? number extends N ? never : N : never
  • @captain-yossarian - 哦哦哦,太酷了!!我会添加它。
  • 关于您的警告,我认为重要的是要记住重载是 type 上的 静态(编译时)匹配,而不是动态(运行时)匹配。这在 TypeScript 中实际上是令人困惑的,因为重载只发生在签名中,而重载行为的实际 实现 当然是在运行时基于值,因为没有静态类型这样的东西在 ECMAScript 中。
  • 在将重载解析作为语言语义的一部分的语言中,重载由编译器在编译时根据类型解析。
  • 谢谢!看起来我的回调签名几乎就在那里。无论如何,文字的警告让我重新思考我的方法。非常感谢您出色(而且快速!)的回答!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-19
  • 1970-01-01
  • 1970-01-01
  • 2015-01-16
  • 2011-08-29
  • 2014-01-09
相关资源
最近更新 更多