typescript 交集函数类型

时间:2021-03-05 14:01:41

标签: typescript

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

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 不是 never 而类型 x 中的参数 i 不是 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

2 个答案:

答案 0 :(得分:2)

注意:非原始类型通常用大写字母大写;这将它们与原始类型和变量/属性名称区分开来。我将在接下来的内容中使用这个约定。

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


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

type G = 1 & 2 // never

never 这样的空交叉点减少到 1 & 2 是在 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 被急切地缩减为 never,就像上面的 G 一样。


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

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 以获取支持此功能的长期功能请求)。< /p>

  • 即使 TypeScript 没有将函数交集解释为重载,H 也不应该是 never。函数类型的参数类型是逆变(请参阅我对 this question 的回答以获取解释。您还可以阅读 the TS handbook description of the --strictFunctionTypes compiler flag 中的函数参数逆变)。逆变将事物变成它们的dual;如果F<T>T中是逆变的,那么F<T | U>等价于F<T> & T<U>,而F<T & U>等价于F<T> | F<U>。因此,一致的类型系统,当询问函数的交集 的参数类型时,将返回函数参数类型的联合。而 1 | 2 不是 never

  • 即使 H 一个完全无人居住的类型,目前 TypeScript 仅在特定情况下将交集减少到 never,而函数交集不是其中之一那些。如果您查看 compiler source code file checker.ts,它会说“如果一个交集类型包含类型 never,或者多个单元类型或一个对象类型和一个可空类型(nullundefined),或类似 string 的类型和已知的非类似 string 的类型,或类似 number 的类型和已知的类型non-number-like, or a symbol-like type and type known to non-symbol-like, or a void-like type and a type known是非 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: 2) => 0 时只看到 (x: infer L) => 0,因此 L 被推断为 2。 “正确”的做法可能是联合 1 | 2,但这不会发生。有关此限制的一个实例,请参阅 microsoft/TypeScript#27027


Playground link to code

答案 1 :(得分:0)

以防万一

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

您将 1 指定为 x 的类型,也称为文字类型。文字类型紧挨着 never 最小的一组值。 never 和文字类型 1 之间的区别在于 never 是一个空集,而 1 集只有一个值。所以 never !== 1

这里

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

参数x的类型是never