基于泛型的变化函数重载

时间:2019-09-27 21:04:03

标签: typescript overloading

如何提供基于具有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

1 个答案:

答案 0 :(得分:2)

您并不是真正按照自己的想法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 };

其中type参数未使用。 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>的子类型,这意味着您可以调用{{1 }},没有错误。值publish(NeverMessage, "hello there");将被视为有效的NeverMessage。为防止,您需要制作Message<"hello there"> invariant in T,如果您通过这样的函数属性启用了--strictFunctionTypes,则可以完成此操作:< / p>

Message<T>

您还需要扩展实现签名:

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 {
  // ...
}

...呃,但是我离题了,因为您没有问这个。 ?

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


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

Link to code