基于泛型类型的 Vary 函数重载

Vary function overload based on generic type

如何提供基于具有 never 类型的参数之一的函数重载?

type Message<T> = { id: string };

function publish<T>(message: Message<T>, payload: T) {
  // ...
}

如果 Tnever,这意味着消息永远不会有有效载荷,因此我不希望函数期望 data 参数。

我重载了函数以提供 never 案例的替代签名,这使得 payload 参数可选。

function publish(message: Message<never>, payload?: never): void

function publish<T>(message: Message<T>, payload: T): void {
  // ...
}

这适用于 Message<never> 情况,但会中断所有其他调用:

let NeverMessage: Message<never> = { id: "never-message" };
let NumberMessage: Message<number> = { id: "number-message" };

publish(NeverMessage);
// All good!

publish(NumberMessage, 10);
// function publish(message: Message<never>, payload?: undefined): void
// Argument of type '10' is not assignable to parameter of type 'undefined'

如何在不使 payload 在两个签名中都可选的情况下重载定义?

Playground

你并不像你想象的那样overloading。一个重载函数有一个有序的 调用签名 列表,它对函数的调用者可见并且没有实现(它们以 ; 而不是 {...} 结尾),以及一个对函数的实现可见的 实现签名 。您表面上希望调用者而不仅仅是实现来查看 publish<T>(message: Message<T>, payload: T): void 签名。如果是这样,你需要做这样的事情:

// call signatures
function publish(message: Message<never>, payload?: never): void;
function publish<T>(message: Message<T>, payload: T): void;

// implementation signature
function publish<T>(message: Message<T>, payload: T): void {
  // ...
}

如前所述,这应该可以解决您的问题。


顺便说一句,请注意 discouraged 拥有像

这样的通用类型
type Message<T> = { id: string };

其中未使用类型参数。 TypeScript 的类型系统主要是 structural 而不是名义上的,这意味着如果两个类型具有相同的 结构 ,那么它们是相同的类型,即使您使用不同的名称来引用他们。在这种情况下,Message<never>Message<number> 都是 { id: string },因此它们是同一类型。

编译器可能会将显式类型为 Message<never> 的表达式与显式类型为 Message<number> 的表达式区别对待,并且您的重载将按您想要的方式运行。但不能保证这将始终有效,并且 weird stuff 可能会在无效时发生。

这里的传统智慧是在类型结构的某处使用类型参数。甚至像

type Message<T> = { id: string; __messageType?: T };

有时足以让事情正常进行,即使在运行时不需要任何 __messagetype 属性。

你可能还需要小心子类型和超类型,因为即使上面的 Message<T> 定义也有 Message<never> 是任何 Message<T> 的子类型,这意味着你可以调用 publish(NeverMessage, "hello there");没有错误。值 NeverMessage 将被视为有效的 Message<"hello there">。为了防止 that 你需要通过函数 属性 启用 Message<T> invariant in T, which can be accomplished if you've got --strictFunctionTypes 像这样:

type Message<T> = { id: string; __messageType?: (x: T) => T };

而且您还需要扩大实施签名:

function publish(message: Message<never>, payload?: never): void;
function publish<T>(message: Message<T>, payload: T): void;
function publish<T>(message: Message<T> | Message<never>, payload?: T): void {
  // ...
}

这会导致

publish(NeverMessage); // okay
publish(NumberMessage, 10); // okay
publish(NeverMessage, "hello there"); // error!

...呃,但我离题了,因为你不是在问这个。

你的主要问题的答案是记得添加一个单独的实现签名。


好的,希望对您有所帮助;祝你好运!

Link to code