Consider the types FooBar1
and FooBar2
defined as follows:
type Foo = { foo: string };
type Bar = { bar: number };
type FooBar1 = Foo & Bar;
type FooBar2 = { foo: string; bar: number };
FooBar1
and FooBar2
?My attempt / research:
tsd
- see here)tsd
- see here){ foo } & { bar }
automatically into { foo, bar }
, while it does collapse other complex types to simpler forms, such as NonNullable<string | undefined>
to string
:// |------------------------------------------------------------|
// | let x: { |
// | a: string; |
// | } & { |
// | b: string; |
// | } |
// | -----------------------------------------------------------|
// ^
// | When hovering `x` here:
// |
let x: { a: string } & { b: string };
Edit: Difference between extending and intersecting interfaces in TypeScript? has been suggested as a duplicate but I disagree. Instead of comparing an intersection to the extension of an interface, I am comparing an intersection to another raw type, no interfaces or extensions involved.
Intersection types are closely related to union types, but they are used very differently. An intersection type combines multiple types into one. This allows you to add together existing types to get a single type that has all the features you need.
An intersection type is a type that merges several kinds into one. This allows you to combine many types to create a single type with all of the properties that you require. An object of this type will have members from all of the types given. The '&' operator is used to create the intersection type.
Interfaces are basically a way to describe data shapes, for example, an object. Type is a definition of a type of data, for example, a union, primitive, intersection, tuple, or any other type.
TypeScript allows us to not only create individual types, but combine them to create more powerful use cases and completeness. There's a concept called “Intersection Types” in TypeScript that essentially allows us to combine multiple types.
In your example, we can say that FooBar1
and FooBar2
are equal. And we can indeed prove that:
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
But for a general answer, we can only say that they are not always equal. Why? Because intersections can resolve to never
in some cases. If ts finds an intersection to be valid, it proceeds with it, otherwise returns 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}
Here type O3
resolved to never
because b
is 3
and it cannot overlap with 2
. Let's change our example to show that an intersection would work if you had:
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}
This example also highlights how intersections work -- like union intersections. TypeScript will go over all the property types and intersect them. We've forced TypeScript to compute the result of the intersection with A.Compute
.
In short, if ts cannot overlap ALL OF the members, then the product of that intersection is never
. For this reason, it might not be suitable as a merging tool:
type O3 = {a: 1, b: 2} & {b: 3} // resolved to `never`
So remember, &
is not a merging tool. A & B
is only equal to {...A, ...B}
only if they have keys that do not overlap. If you need a merging type, use O.Merge
.
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