【问题标题】:Inference of union function types联合函数类型的推断
【发布时间】:2021-03-02 10:51:29
【问题描述】:

假设我有类似的架构:

type GetDog = () => { animal: string; bark: boolean };
const getDog: GetDog = () => ({ animal: 'dog', bark: true });

type GetCat = () => { animal: string; meow: boolean };
const getCat: GetCat = () => ({ animal: 'cat', meow: true });

type AnimalFactory =
  | ((callback: GetDog) => ReturnType<typeof getDog>)
  | ((callback: GetCat) => ReturnType<typeof getCat>);

const calmAnimalFactory: AnimalFactory = (callback) => {
  // Some fancy stuff
  return callback();
};

calmAnimalFactory 作为参数应该接受getDog 函数或getCat 函数,然后返回相应的值。问题是我猜类型推断不像我想象的那样工作。 calmAnimalFactory 内部的回调不会推断GetDog | GetCat 的类型,而是any。我认为 Typescript 应该确定 calmAnimalFactory 的类型,而 calmAnimalFactory(getDog) 应该输入为

((callback: GetDog) => ReturnType<typeof getDog>)

calmAnimalFactory(getCat) 应输入为

((callback: GetCat) => ReturnType<typeof getCat>)

我希望有可能实现,但我不知道为什么它不能那样工作。

【问题讨论】:

    标签: typescript typescript-typings


    【解决方案1】:

    这里发生了一些事情。首先,我认为如果我必须继续输入typeof getXXXReturnType&lt;typeof getXXX&gt;,我会发疯的,所以我将定义新的DogCat 接口对应于getDog()getCat() 返回的内容,并专门使用它们:

    interface Dog extends ReturnType<GetDog> { };
    interface Cat extends ReturnType<GetCat> { }
    

    您可以并且可能应该首先定义这些DogCat 类型,然后根据它们编写getCat()getDog()

    interface Dog {
      animal: string;
      bark: boolean;
    }
    
    type GetDog = () => Dog;
    const getDog: GetDog = () => ({ animal: 'dog', bark: true });
    
    interface Cat {
      animal: string;
      meow: boolean;
    }
    type GetCat = () => Cat;
    const getCat: GetCat = () => ({ animal: 'cat', meow: true });
    

    但无论哪种方式都可以。


    接下来,如果您希望 AnimalFactory 在传递 GetCat 参数时返回 Cat并且您希望在传递 Dog 时返回 Dog GetDog 参数,那么您想使用 intersection (&amp;) 而不是 union (|)。如果你使用工会,你是说动物工厂要么GetCat变成Cat要么 GetDog 变为 Dog,但不一定两者兼而有之。所以我们想要这里的交集:

    type AnimalFactory =
      ((callback: GetDog) => Dog)
      & ((callback: GetCat) => Cat);
    

    接下来,编译器无法很好地推断未注释和未断言的函数实现是否符合这种签名的交集。这样的推理相当于能够正确地对overloaded functions 进行类型检查,而编译器根本无法做到这一点(例如,请参阅microsoft/TypeScript#35338 中的this comment)。

    因此,如果您只是尝试根据上下文键入 callback 参数,编译器将失败,并最终隐式使用 any,如您所见。


    最好使用generic 函数实现,声称能够将() =&gt; T 类型的callback 用于泛型T extends Cat | Dog,然后生成T。这是编译器可以类型检查的东西:

    const calmAnimalFactory: AnimalFactory =
      <T extends Cat | Dog>(callback: () => T) => {
        return callback();
      };
    

    编译器能够验证&lt;T extends Cat | Dog&gt;(callback: () =&gt; T) =&gt; T 类型的函数是否可以分配给AnimalFactory 的两个调用签名,因此它编译时没有错误。


    事实上,根据您的用例,您可能希望通过删除对CatDog 的依赖来使AnimalFactory 尽可能通用,而是提出一些基本类型,例如Animal,如:

    interface Animal {
      animal: string;
    }
    type UniversalAnimalFactory = <T extends Animal>(callback: () => T) => T;
    const universalAnimalFactory: UniversalAnimalFactory = callback => callback();
    

    UniversalAnimalFactory 可以做任何 AnimalFactory 可以做的事情(GetCat in,Cat out;和 GetDog in,Dog out),但它也可以为您拥有的动物做任何其他事情'甚至还没有想到:

    const getFish = () => ({ animal: "fish", swim: true });
    universalAnimalFactory(getFish).swim // works
    

    Playground link to code

    【讨论】:

    • 谢谢!这是非常有用的回应,有很好的建议:)
    【解决方案2】:

    您可以使用泛型对此进行正确输入。我们说calmAnimalFactory 依赖于代表回调类型的通用T。这个T 必须是一个接受零参数并返回任何内容的函数。

    现在我们可以说calmAnimalFactorycallback 参数是T,并且函数的返回类型与回调的返回类型相同。

    const calmAnimalFactory = <T extends () => any>(callback: T): ReturnType<T> => {
      // Some fancy stuff
      return callback();
    };
    
    const dog = calmAnimalFactory(getDog); // type: { animal: string; bark: boolean; }
    const cat = calmAnimalFactory(getCat); // type { animal: string; meow: boolean; }
    

    相同想法的稍微不同的版本是让泛型T 指代所创建动物的类型。这使得要求回调的返回必须适合某个基本的Animal 接口变得更容易。

    interface Animal {
      animal: string;
    }
    
    const calmAnimalFactory = <T extends Animal>(callback: () => T): T => {
      // Some fancy stuff
      return callback();
    };
    

    catdog 的类型仍然相同。

    Typescript Playground Link

    【讨论】:

    • 谢谢!这也解决了我的问题:)
    猜你喜欢
    • 2022-01-07
    • 1970-01-01
    • 1970-01-01
    • 2018-07-26
    • 2020-09-30
    • 1970-01-01
    • 2019-04-10
    • 1970-01-01
    • 2021-10-05
    相关资源
    最近更新 更多