【问题标题】:typescript intersection types of function打字稿交集类型的功能
【发布时间】:2021-03-05 23:27:06
【问题描述】:

我正在使用打字稿并遇到了一些问题。最简单的演示是:

type g = 1 & 2 // never
type h = ((x: 1) => 0) & ((x: 2) => 0) // why h not never
type i = ((x: 1 & 2) => 0)// why x not never

我不明白为什么h 类型不是neveri 类型中的参数x 不是never

type e = (((x: 1) => 0) & ((x: 2) => 0)) extends (x: infer L) => 0 ? L : never; //  why e is 2 not never or 1?

另外,不明白为什么输入e2 而不是never

【问题讨论】:

    标签: typescript


    【解决方案1】:

    注意:非原始类型通常用 UpperCamelCase 编写;这将它们与原始类型和变量/属性名称区分开来。我将在下面使用这个约定。

    这个问题有很多部分,所以它的答案也会有很多部分。


    让我们先解决简单的问题:

    type G = 1 & 2 // never
    

    1 & 2 这样的空交叉点减少到never 是在microsoft/TypeScript#31838 中实现的,并在TypeScript 3.6 中发布。在此之前,1 & 2 的处理方式与never 非常相似,因为您永远无法找到满足它的值。 1 & 2never 之间在概念上确实没有区别,尽管编译器实现细节可能会导致两者的处理方式不同。


    下一个:

    type I = ((x: 1 & 2) => 0) // why x not never
    

    x never,但减少是延迟直到你真正使用它:

    type IParam = Parameters<I>[0]; // never
    

    此延迟在 microsoft/TypeScript#36696 中实现,并随 TypeScript 3.9 发布。在此之前,x 就像上面的G 一样被急切地缩减为never


    现在来看一些更复杂的例子:

    type H = ((x: 1) => 0) & ((x: 2) => 0) // why H not never
    

    H 不是never 实际上有很多原因:

    • TypeScript 特有的原因是因为函数类型的交集被认为与具有多个调用签名的 overloaded function 相同:

      declare const h: H;
      // 1/2 h(x: 1): 0
      // 2/2 h(x: 2): 0
      h(1); // okay
      h(2); // okay
      h(3); // error, no overload matches this call
      h(Math.random() < 0.5 ? 1 : 2); // error, no overload matches this call
      

      注意h 如何显示为具有两个重载的函数;一个接受1 参数,另一个接受2 参数。它不接受3,也不接受1 | 2,即使从纯类型系统的角度来看它可能应该接受(请参阅microsoft/TypeScript#14107 以获得支持此功能的长期功能请求)。

    • 即使 TypeScript 没有将函数交集解释为重载,H 也不应该是 never。函数类型在其参数类型中是逆变的(有关解释,请参阅我对this question 的回答。您还可以在the TS handbook description of the --strictFunctionTypes compiler flag 中了解函数参数的逆变)。逆变将事物变成他们的dual;如果F&lt;T&gt;T中是逆变的,那么F&lt;T | U&gt;等价于F&lt;T&gt; &amp; T&lt;U&gt;,而F&lt;T &amp; U&gt;等价于F&lt;T&gt; | F&lt;U&gt;。所以一个一致的类型系统,当被问及函数的 intersection 的参数类型时,将返回函数参数类型的 union。而1 | 2 不是never

    • 即使H 一个完全无人居住的类型,目前 TypeScript 只在特定情况下将交集减少到 never,而函数的交集不是其中之一。如果您查看compiler source code file checker.ts,它会说“如果一个交集类型包含never 类型,或多个单元类型或一个对象类型和一个可为空的类型(nullundefined),则认为它为空,或string-like 类型和已知非string-like 类型,或number-like 类型和已知非number-like 类型,或symbol- like 类型和已知非symbol-like 的类型,或void-like 类型和已知非void-like 的类型,或非原始类型和已知类型原始。”这些都没有说“两个不兼容的函数类型”,所以它没有被简化为never


    最后一个:

    type E = (((x: 1) => 0) & ((x: 2) => 0)) extends (x: infer L) => 0 ? L : never;
    //  why E is 2 not never or 1?
    

    回想一下,函数的交集被认为是重载函数。 TypeScript 的一个已知设计限制是,在重载函数类型上使用类型推断时,编译器不会尝试通过找出哪个调用签名与预期推断最匹配来解决重载。 它只使用最后一个调用签名并忽略所有其余部分。在E 中,这意味着编译器在匹配(x: infer L) =&gt; 0 时只能看到(x: 2) =&gt; 0,因此L 被推断为2。要做的“正确”事情可能是联合1 | 2,但这不会发生。有关此限制的一个实例,请参阅 microsoft/TypeScript#27027


    Playground link to code

    【讨论】:

    【解决方案2】:

    万一

    type h = ((x: 1) => 0) & ((x: 2) => 0)
    

    您将1 分配为x 的类型,也称为文字类型。文字类型紧邻never 最小的一组值。 never 和文字类型 1 之间的区别在于 never 是一个空集,但 1 集只有一个值。这样never !== 1

    这里

    type i = (x: 1 & 2) => 0; 
    

    参数类型xnever

    【讨论】:

      猜你喜欢
      • 2019-04-10
      • 2022-01-13
      • 2017-06-12
      • 2019-04-13
      • 2022-01-11
      • 1970-01-01
      • 1970-01-01
      • 2016-12-08
      • 2022-01-24
      相关资源
      最近更新 更多