"Do you want coffee or tea?"
In ordinary speech, "or" means "exclusive or." Only programmers and logicians use an inclusive or.
In TypeScript, it's easy to get mixed up between these two:
We usually read the last line as "Type
Thing is a
||), TypeScript's type-level or (
|) is an inclusive or. There's no reason a thing can't be both a
ThingOne and a
Why does this work? It's because TypeScript has a structural type system. Both the
ThingTwo types allow additional properties that aren't declared in their interface (this fact is sometimes obscured by excess property checking):
So what if you really do want an exclusive or? What if you want to keep your
ThingTwos separate? How can you model that?
There's a standard trick, which is to use an optional
never type in your interface to disallow a property:
Now none of the assignments from before pass the type checker (see playground):
This works because no value is assignable to a
never type. But because the property is optional, there's exactly one way out: not having that property.
This isn't just useful for unions. If you want to define a two dimensional vector type, for example, you might want to specifically disallow adding a third dimension:
With this type, you'll get an error if you accidentally pass a three-dimensional vector to a function like
This wouldn't be an error without the
z?: never because the call is structurally valid, even though it's semantically incorrect.
"Tags" are another common way to make an or exclusive:
A string can't be both "one" and "two", so there's no overlap between these types. This means there's no distinction between inclusive and exclusive or. This is one of many great reasons to use tagged unions when you can.
It's a fun exercise to define an exclusive or generic:
Here's what I came up with:
There's one caveat to the optional
never trick that's worth knowing: unless you set the
--exactOptionalPropertyTypes compiler flag (added in TS 4.4), you're allowed to assign
undefined to an optional never field:
So remember: in TypeScript, "or" is a union:
A | B means either
B, or both. Remember that "both" is a possibility, and you should either prevent it or handle it in your code.