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 【问题描述】:考虑类型FooBar1
和FooBar2
定义如下:
type Foo = foo: string ;
type Bar = bar: number ;
type FooBar1 = Foo & Bar;
type FooBar2 = foo: string; bar: number ;
问题:FooBar1
和FooBar2
有什么区别?
我的尝试/研究:
它们可以双向分配给彼此! (手动检查并使用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
使用来自tsc
的isTypeIdenticalTo
来检查两种类型是否相同,但是检查变得非常复杂非常快,所以我没有检查为什么这两种类型不相同。当前的语言版本没有定义这种关系,因此向语言用户询问可能不是一个相关的问题,它只是一个编译器细节。编译器使用一些标志来区分 Object 和 Intersection 类型。这种区别可能与“补码类型”之类的东西相关(例如 not (A & B) == not A | not B)
【参考方案1】:
在您的示例中,我们可以说FooBar1
和FooBar2
是相等的。我们确实可以证明:
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
,因为b
是3
,它不能与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`
请记住,&
不是合并工具。 A & B
仅等于 ...A, ...B
仅当它们具有不重叠的键时。如果需要合并类型,请使用O.Merge
。
【讨论】:
感谢您的回答,但我很困惑。这个问题的直接答案是什么? FooBar1 和 FooBar2 有什么区别? 另外,“intent to merge”和“intent to use &”有什么区别?谢谢:) 正确的答案是它们并不总是相等的。 对于大多数人来说,当他们使用&
时是为了合并两种类型,或者缩小一个联合。但&
的真正意图是相交,而不是合并。这对对象不是很有用,因为它可以很容易地为您生成never
。 &
运算符在对象上的行为与在联合上的行为方式完全相同。需要记住的是,&
在一定程度上可以用于合并,但它不是合并工具。因此,如果您想合并对象,请使用 ts-toolbelt,它使用 &
(内部)负责任地实现这一目标。
您说的是“它们并不总是相等”,我想我明白了,您通常在谈论 & 的用法。但我想了解我的具体例子。 FooBar1 和 FooBar2 相等或不同。是哪个?以上是关于TypeScript 中的交集和合并类型 ` foo & bar ` 和 ` foo, bar ` 之间的区别的主要内容,如果未能解决你的问题,请参考以下文章