Using Typescript Unions With Never and Extends
3 min read
I've reached a point where I'm now really enjoying working with Typescript. I remember one of the senior engineers in my team telling me when I first started that I would find myself being a Typescript convert one day. I didn't really believe it then, as TS seemed to be so much more of a hindrance than help in the early days (it wouldn't even allow me to run my code to debug it!)...but I'm happy to say he was right. โ๐ป
I've reached the tipping point, where it now feels weird to NOT have Typescript switched on for a Javascript repo. There are, for sure, still days when I'm trying to get a specific scenario typed properly and I'm finding it difficult to even know what to Google for... but it gets easier over time as you start to build a bank of knowledge and realise that the same patterns keep cropping up again and again.
One pattern that I keep coming across is this one - let's say I have a Media
component that I need to pass certain props into, for example:
{
id: number;
group: string;
bookTitle: string;
ebook: boolean;
tvShowTitle: string;
platform: string;
songTitle: string;
artist: string;
}
There are certain props that always needs to be present for the Media
component to render, with the rest being conditional on whether certain other props are present:
// Always required
{
id: number;
group: string;
}
// Also need to have either Option 1 or 2 present.
// Option 1: Both required, or neither present
{
bookTitle: string;
ebook: boolean;
}
// Option 2: Both required, or neither present
{
tvShowTitle: string;
platform: string;
}
How do we ask Typescript to check that if say bookTitle
is passed as a prop, ebook
becomes a required prop, whereas tvShowTitle
and platform
should NOT be present? Additionally, we want to let Typescript know that id
and group
should ALWAYS be required.
The answer is through a combination of union types and extends
.
interface MediaBase {
id: string;
group: string;
}
interface BookProps extends MediaBase {
bookTitle: string;
ebook: boolean;
tvShowTitle: never;
platform: never;
}
interface TvShowProps extends MediaBase {
bookTitle: never;
ebook: never;
tvShowTitle: string;
platform: string;
}
export type MediaProps = BookProps | TvShowProps;
MediaProps
here is the final type of the Media
component.
- This can either take the form of
BookProps
orTvShowProps
(union type). - We are explicit in our
BookProps
andTvShowProps
usingnever
to tell Typescript that these props should not be passed in if certain other props are present. - By using
extends MediaBase
, bothBookProps
andTvShowProps
also hasid
andgroup
as required props.