Narrow what a value can be

INPUT · Slides

Unions you tell apart by a marker

01 / 05

typeof cannot tell them apart

All the narrowing so far had typeof in the lead. Text gave you "string" and a number gave you "number", so that was enough to split them.

But it does nothing for a union of objects. Both of them come back as "object". That will not separate "the dog shape" from "the bird shape".

const dog = { name: "Pochi" };const bird = { flies: true };console.log(typeof dog);console.log(typeof bird);

Result

object
object

02 / 05

Give them one shared marker

So you add one property with the same name to both types. Make its type a literal, and give each member a different value. That is your marker.

The marker is on both types, so you can read it even before narrowing. And once an if looks at the marker, inside it you are known to have one of the two. That is why name, which only Dog has, becomes safe to use.

type Dog = {  kind: "dog";  name: string;};type Bird = {  kind: "bird";  flies: boolean;};function show(a: Dog | Bird): void {  if (a.kind === "dog") {    console.log(a.name);  } else {    console.log(a.flies);  }}show({ kind: "dog", name: "Pochi" });show({ kind: "bird", flies: true });

Result

Pochi
true

03 / 05

Without a marker you cannot use either side

Have a look at what happens if you do not give them one. In a type that is only joined with |, you can only use properties both of them have.

Bird has no name, so a.name is stopped right there. The error comes in two lines, and the lower one names which member is missing it.

type Dog = {  name: string;};type Bird = {  flies: boolean;};function show(a: Dog | Bird): void {  console.log(a.name);}

Result

Type error: Property 'name' does not exist on type 'Dog | Bird'.
  Property 'name' does not exist on type 'Bird'.

04 / 05

A marker only works as a literal type

The other pitfall is writing the marker as string. With kind: string, both members merely have "some text or other".

Then writing if (a.kind === "dog") narrows nothing down. Inside it, it is still Dog | Bird, so you get the same error as before. Write the marker as the value itself, like "dog".

type Dog = {  kind: string;  name: string;};type Bird = {  kind: string;  flies: boolean;};function show(a: Dog | Bird): void {  if (a.kind === "dog") {    console.log(a.name);  }}

Result

Type error: Property 'name' does not exist on type 'Dog | Bird'.
  Property 'name' does not exist on type 'Bird'.

05 / 05

Three or more is just the same

More members joined on changes nothing about what you do. Give the marker a different value each time and line them up with else if.

Name it with type and the same type works on parameters and on the contents of arrays. Time to write some. Give them a marker and look at it with an if — that is all.

type Rain = {  weather: "rain";  amount: number;};type Sunny = {  weather: "sunny";  temp: number;};type Weather = Rain | Sunny;function show(w: Weather): void {  if (w.weather === "rain") {    console.log("rain " + w.amount);  } else {    console.log("sunny " + w.temp);  }}show({ weather: "rain", amount: 12 });show({ weather: "sunny", temp: 28 });

Result

rain 12
sunny 28