TypeScript 中的交集和合并类型 ` foo & bar ` 和 ` foo, bar ` 之间的区别

Posted

技术标签:

【中文标题】TypeScript 中的交集和合并类型 ` foo & bar ` 和 ` foo, bar ` 之间的区别【英文标题】:Difference between intersection and merged types ` foo & bar ` and ` foo, bar ` in TypeScriptTypeScript 中的交集和合并类型 ` foo & bar ` 和 ` foo, bar ` 之间的区别 【发布时间】:2020-08-03 10:15:28 【问题描述】:

考虑类型FooBar1FooBar2定义如下:

type Foo =  foo: string ;
type Bar =  bar: number ;
type FooBar1 = Foo & Bar;
type FooBar2 =  foo: string; bar: number ;

问题:FooBar1FooBar2有什么区别?


我的尝试/研究:

它们可以双向分配给彼此! (手动检查并使用tsd - see here) 不过,它们彼此并不相同! (检查tsd - see here) VSCode 的智能感知不会自动将 foo & bar 折叠成 foo, bar ,但它确实会将其他复杂类型折叠成更简单的形式,例如将NonNullable<string | undefined> 折叠成string
// |------------------------------------------------------------|
// | let x:                                                    |
// |     a: string;                                             |
// |  &                                                       |
// |     b: string;                                             |
// |                                                           |
// | -----------------------------------------------------------|
//  ^
//  | When hovering `x` here:
//  |
let x:  a: string  &  b: string ;

编辑: Difference between extending and intersecting interfaces in TypeScript? 已被建议重复,但我不同意。我不是将交集与接口的扩展进行比较,而是将交集与另一种原始类型进行比较,不涉及接口或扩展。

【问题讨论】:

在注意到两票结束后,我编辑了问题,使其更简单、更切题。希望这些选票现在可以撤回。让我知道我是否应该改进其他任何事情。 您使用哪个 typescript 编译器版本?使用 3.8.3 编译时不会出现预期的警告(您在 Github 上的尝试)。 @WolverinDEV 嗨,谢谢,是的,我也使用 3.8.3,它可以编译,是的,但编译不是问题。 我很确定这是 TSD 中的一个错误,它只是无法确定这两个是相同的。 tsd 使用来自tscisTypeIdenticalTo 来检查两种类型是否相同,但是检查变得非常复杂非常快,所以我没有检查为什么这两种类型不相同。当前的语言版本没有定义这种关系,因此向语言用户询问可能不是一个相关的问题,它只是一个编译器细节。编译器使用一些标志来区分 Object 和 Intersection 类型。这种区别可能与“补码类型”之类的东西相关(例如 not (A & B) == not A | not B) 【参考方案1】:

在您的示例中,我们可以说FooBar1FooBar2 是相等的。我们确实可以证明:

type Equals<A, B> =
    A extends B
    ? B extends A
      ? true
      : false
    : false

type test0 = Equals<a: 1 & b: 2, a: 1, b: 2> // true

但对于一般的答案,我们只能说它们并不总是相等的。为什么?因为在某些情况下,交叉点可以解析为never。如果 ts 发现一个交集是有效的,它会继续它,否则返回never

import O from "ts-toolbelt"

type O1 = a: 1, b: 2
type O2 = a: 1 & b: 2       // intersects properly
type O3 = a: 1, b: 2 & b: 3 // resolved to `never`

type test0 = Equals<O1, O2>
// true

type test1 = O.Merge<a: 1, b: 2, b: 3, c: 4>
// a: 1, b: 2, c: 4

在这里键入O3 解析为never,因为b3,它不能与2 重叠。让我们更改我们的示例,以表明如果您有交叉路口将起作用:

import A from "ts-toolbelt"

type O4 = A.Compute<a: 1, b: number & b: 2> // a: 1, b: 2
type O5 = A.Compute<a: 1, b: 2 & b: number> // a: 1, b: 2

这个例子还强调了交叉点的工作原理——比如联合交叉点。 TypeScript 将遍历所有属性类型并将它们相交。我们已经强制 TypeScript 计算与A.Compute 相交的结果。

简而言之,如果 ts 不能与所有成员重叠,则该交集的乘积为never。因此,它可能不适合作为合并工具:

type O3 = a: 1, b: 2 & b: 3 // resolved to `never`

请记住,&amp; 不是合并工具。 A &amp; B 仅等于 ...A, ...B 仅当它们具有不重叠的键时。如果需要合并类型,请使用O.Merge

【讨论】:

感谢您的回答,但我很困惑。这个问题的直接答案是什么? FooBar1 和 FooBar2 有什么区别? 另外,“intent to merge”和“intent to use &”有什么区别?谢谢:) 正确的答案是它们并不总是相等的。 对于大多数人来说,当他们使用&amp; 时是为了合并两种类型,或者缩小一个联合。但&amp; 的真正意图是相交,而不是合并。这对对象不是很有用,因为它可以很容易地为您生成never&amp; 运算符在对象上的行为与在联合上的行为方式完全相同。需要记住的是,&amp; 在一定程度上可以用于合并,但它不是合并工具。因此,如果您想合并对象,请使用 ts-toolbelt,它使用 &amp;(内部)负责任地实现这一目标。 您说的是“它们并不总是相等”,我想我明白了,您通常在谈论 & 的用法。但我想了解我的具体例子。 FooBar1 和 FooBar2 相等或不同。是哪个?

以上是关于TypeScript 中的交集和合并类型 ` foo & bar ` 和 ` foo, bar ` 之间的区别的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Typescript 中覆盖交集类型的属性?

Typescript 接口合并以使返回类型更具体

TypeScript 类工厂需要一个交集

LayaBox---TypeScript---声明合并

TypeScript 声明合并 - 更改属性类型

TypeScript学习笔记