【问题标题】:Vary function overload based on generic type根据泛型类型改变函数重载
【发布时间】:2019-09-27 21:04:03
【问题描述】:

如何提供基于 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&lt;never&gt; 的情况,但会中断所有其他调用:

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

【问题讨论】:

    标签: typescript overloading


    【解决方案1】:

    您并不是真正的overloading 功能,就像您认为的那样。重载函数具有 调用签名 的有序列表,这些调用签名对函数的调用者可见并且没有实现(它们以 ; 结尾而不是 {...}),以及单个 实现签名对函数的实现可见。您表面上希望调用者而不只是实现看到publish&lt;T&gt;(message: Message&lt;T&gt;, 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&lt;never&gt;Message&lt;number&gt; 都是{ id: string },因此它们是相同的类型。

    编译器可能会将显式键入为Message&lt;never&gt; 的表达式与显式键入为Message&lt;number&gt; 的表达式区别对待,并且您的重载将按照您想要的方式运行。但不能保证这将永远有效,weird stuff 可能会在它不起作用时发生。

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

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

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

    您可能还需要注意子类型和超类型,因为即使上面的Message&lt;T&gt; 定义有Message&lt;never&gt; 是任何Message&lt;T&gt; 的子类型,这意味着您可以毫无错误地调用publish(NeverMessage, "hello there");。值NeverMessage 将被视为有效的Message&lt;"hello there"&gt;。为了防止,您需要创建Message&lt;T&gt; invariant in T,如果您通过这样的函数属性启用了--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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-04-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-12
      相关资源
      最近更新 更多