DEV Community

Anton Holmberg
Anton Holmberg

Posted on

Understanding TypeScripts Exclude

I recently started to do more TypeScript. I have plenty of previous experiences
with typed languages but there were still some things in TypeScript that I didn't
really feel comfortable with at first.

That Weird Exclude Type

While reading release notes for TypeScript 2.8 I stumbled across Omit. Not
knowing what it was I set out to understand it. However, the problem grew since
I found that Omit was defined as a combination of Pick and Exclude. I just
couldn't for the life of me figure out what Exclude did.

Most of the articles I found about Exclude would show an example of how it was
used in conjunction with another type. It felt like they sort of assumed that
the reader already knew what Exclude did.

Lets Start With Union Types

So TypeScript has this awesome feature called union types. I think it is
easier to show an example of a union type rather than explaining it in text.

type Language = "swedish" | "danish" | "english" | "french":

const firstLanguage: Language = "swedish";
const secondLanguage: Language = "english";

// Will not compile
const thirdLanguage = "meowing"

So in the example above we create a type called Language. A variable of type
Language can now only be one of the languages we defined in the type. In this
case meowing is not an acceptable language and therefore the program above
will not compile.

So What Is This Exclude Thing?

This is when Exclude comes in. Exclude takes two union types and, sort of,
subtracts the values in the second union type from the first union type.

type Language = "swedish" | "danish" | "english" | "french":
type NordicLanguage = Exclude<Language, "english" | "french">;

const firstLanguage: NordicLanguage = "swedish";
// This will not compile
const secondLanguage: NordicLanguage = "english";

So in the above example we create another type called NordicLanguage. This
type can take on all the same values as Language except for the excluded values
english and french. This is more or less the same as writing.

type Language = "swedish" | "danish" | "english" | "french":
type NordicLanguage = "swedish" | "danish";

A Cool Use Case

So I recently had a problem where I had an object that contained multiple keys of
the same type. I also wanted to store which keys was currently
active/selected.

As it turned out; this perfect case for Exclude.

type AvailableArea = Exclude<keyof Map, 'selectedArea'>;

type Climate = 'grass' | 'snow' | 'sand' | 'water';
interface Area {
  climate: Climate;
}

interface Map {
  selectedArea: AvailableArea;
  north: Area;
  south: Area;
  west: Area;
  east: Area;
}

The first thing that we need to understand if what keyof means.

// Same as: type keys = "selectedArea" | "north" | "south" | "west" | "east";
type keys = keyof Map;

interface Map {
  selectedArea: AvailableArea;
  north: Area;
  south: Area;
  west: Area;
  east: Area;
}

So now that we have that down the question is: Do we really want selectedArea
to be able to refer to it self? In this case the answer was no. If I create a
union type with the key names hard coded, what if I start adding more areas
like southWest? These questions lead me to the conclusion that probably it is
best if I use Exclude here.

We know that keyof returns a union type where the values can be any of the
keys in the object. All we need to do now is to "exclude" selectedArea and we
should be left with exactly what we want!

type AvailableArea = Exclude<keyof Map, 'selectedArea'>;

This gives me the possibility to include more areas in the future and still keep
type safety throughout my application.

Closing Thoughts

Hopefully someone found this useful in some way. Next time I might cover Pick
but there are plenty of tutorials out there for that and once I understood
Exclude I found that Pick wasn't that hard to grasp.

Top comments (0)