【问题标题】: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)

标签: typescript types


【解决方案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 相等或不同。是哪个?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-23
  • 1970-01-01
  • 2022-11-04
  • 1970-01-01
  • 2018-02-04
相关资源
最近更新 更多