Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between intersection and merged types `{ foo } & { bar }` and `{ foo, bar }` in TypeScript

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 };

Question: What is the difference between FooBar1 and FooBar2?


My attempt / research:

  • They are bidirectionally assignable to each other! (checked manually and with tsd - see here)
  • Still, they are not identical to each other! (checked with tsd - see here)
  • VSCode's intellisense does not collapse { 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.

like image 801
Pedro A Avatar asked Apr 20 '20 19:04

Pedro A


People also ask

When would you use an intersection type instead of a union type?

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.

What are intersection types in TypeScript?

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.

What is the difference between interface and type in TypeScript?

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.

Is it possible to combine types in TS?

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.


1 Answers

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.

like image 133
millsp Avatar answered Oct 12 '22 12:10

millsp