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.