1

A union type describes a value that can be one of several types.

I want to define type union of A and B types. As far as I understood from docs the resulting type should be the type that represents A or B type. Surprisingly I'm able to have both paramA and paramB in UnitedAB type. Why does this happen and how do I create the type that will return type A or type B result?

type A = {
  paramA: any;
};

type B = {
  paramB: any;
};

type UnitedAB = A | B;

const a: UnitedAB = { paramA: 1, paramB: 2 }; //OK (WHY?)
const b: UnitedAB = { paramA: 1}; //ok
const c: UnitedAB = { param12: 1}; //error
crashmstr
  • 28,043
  • 9
  • 61
  • 79
godblessstrawberry
  • 4,556
  • 2
  • 40
  • 58
  • Duplicate of [Does Typescript support mutually exclusive types?](https://stackoverflow.com/questions/42123407/does-typescript-support-mutually-exclusive-types) – jcalz Dec 03 '19 at 18:52
  • @jcalz this might be easier to find for beginners as me + none of answers in that question duplicates current answer – godblessstrawberry Dec 03 '19 at 20:05
  • Also possibly duplicate of [Defining a choice of index type on an interface](https://stackoverflow.com/a/54777729/2887218). It seems to me that when you say "`A` or `B`" you mean *exclusive* or, which isn't a built-in type operator in TS. If the linked questions don't address your problem, let me know. As far as I can tell the answer below doesn't address this issue, but I'd be happy to be shown otherwise. – jcalz Dec 03 '19 at 20:34

1 Answers1

2

The important part here is later on in the documentation

Union types can be a bit tricky here, but it just takes a bit of intuition to get used to. If a value has the type A | B, we only know for certain that it has members that both A and B have. In this example, Bird has a member named fly. We can’t be sure whether a variable typed as Bird | Fish has a fly method. If the variable is really a Fish at runtime, then calling pet.fly() will fail.

Normally, you would knowingly have an A or a B and assign it to a property or parameter that accepts A | B but also have some way for that code to know or discern later which of A or B it actually is.

The next section in the documentation, Type Guards and Differentiating Types covers ways to do this.

Here is an example:

class A {
  type = 'A';
  paramA: any;
};

class B {
  type = 'B';
  paramB: any;
};

type UnitedAB = A | B;
function isA(aOrB: A | B): aOrB is A {
  return aOrB.type === 'A';
}

function isB(aOrB: A | B): aOrB is B {
  return aOrB.type === 'B';
}

const a = new A();

function takesAOrB(aOrB: A | B): void {
  if (isA(aOrB)) {
    console.log(aOrB.paramA);
  } else if (isB(aOrB)) {
    console.log(aOrB.paramB);
  }
}

Typescript Playground

crashmstr
  • 28,043
  • 9
  • 61
  • 79
  • any way to get `A or B` type? so user will not be able to use `paramA` and `paramB` simultaneously on resulting type? – godblessstrawberry Dec 03 '19 at 18:45
  • I added to my answer. You would use type narrowing functions that "know" how to discern an `A` from a `B`, and then your code can use the `A` properties or `B` properties only. – crashmstr Dec 03 '19 at 18:47
  • I don't see that this answer addresses the question: why does the type `A | B` accept values assignable to *both* `A` *and* `B`, also known as `A & B`? The answer is that the "or" in a union type is *inclusive*, not *exclusive*. Exclusive object unions are more complicated to express in TypeScript but possible with conditional types, as shown in the answer to the linked question above. – jcalz Dec 03 '19 at 19:33
  • @jcalz yes, that problem can be solved, but we don’t need to if we focus on declaring / creating objects of a narrow type before using them in a union context. I write code like this all of the time and never found the need to use an “exclusive object union”. I’m sure use-cases exist, but I do not see a critical need for it here. Hence, I’ve tried to answer how one can use a Typescript Union Type to deal with something that is one type or the other. – crashmstr Dec 03 '19 at 22:25