From what I know of set theory, when you take a union of two sets, you overlay two sets and take the resulting set. When you take the intersection of two sets, you overlay two sets and take the parts that overlap with each other.
For numbers this works just fine:
type SomeUnion = (1 | 2 | 3) | (2 | 3 | 4)
const someUnion: SomeUnion // typeof someUnion is 1 | 2 | 3 | 4
type SomeIntersect = (1 | 2 | 3) & (2 | 3 | 4)
const someIntersect: SomeIntersect // typeof someIntersect is 2 | 3
For object keys the intersection and union operates work quite unintuitively in my opinion.
type ObjUnion = { one: string, two: string } | { two: string, three: string }
const objUnionKeys: keyof ObjUnion // typeof objUnionKeys is 'two' while I would expect it to be all keys -> 'one' | 'two' | 'three'
type ObjIntersection = { one: string, two: string } & { two: string, three: string }
const objIntersectionKeys: keyof ObjIntersection // typeof objIntersectionKeys is 'one' | 'two' | 'three' while I would expect it only to be the keys in common -> 'one'
I imagine that there is a solid reason for why it works like this. Can anyone fill me in?
You're right that the behaviour of objects unions and intersections paradoxically seems to behave in the exact opposite way to that of literals but it actually makes perfect sense :) let's dive into why!
If you have a union type for string or number literals à la
type SomeUnion = 1 | 2 | 3 | 2 | 3 | 4
what you're saying is that SomeUnion
could be any one of these numbers.
When you have
type SomeIntersect = (1 | 2 | 3) & (2 | 3 | 4)
what you're saying is that SomeIntersect
has to satisfy the constraints of both groups. In this case the only numbers that satisfy both are 2 and 3, hence the above is equivalent to type SomeIntersect = 2 | 3
The semantics of unions and intersections are different for objects though.
When you have
type ObjUnion = { one: string, two: string } | { two: string, three: string }
What you're saying is that ObjUnion
could have either shape - meaning that the only field you know for sure exists on ObjUnion
is "two"
. The others may or may not exist depending on which of the two shapes it actually is. But you do have certainty that { two: string }
exists on the object.
When it comes to
type ObjIntersection = { one: string, two: string } & { two: string, three: string }
what you're saying is that ObjIntersection
has to have all 3 fields in the two object types, otherwise it wouldn't satisfy the constraints of the intersection.
This means that if you have an object of type ObjIntersection
you know that it has all 3 fields and so TypeScript will let you access any one of them without a problem!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With