Typescript generic with union
2019-11-11
Click here to see the initial setup
enum MessageTypes {
Success,
DuplicateEmail,
}
interface ContentMapping {
[MessageTypes.Success]: {
data: any;
};
[MessageTypes.DuplicateEmail]: {
email: string;
};
}
interface Message<Type extends MessageTypes> {
type: Type;
content: ContentMapping[Type];
}
We'll compare the following two types:
type Option1 = Message<MessageTypes.DuplicateEmail | MessageTypes.Success>;
type Option2 =
| Message<MessageTypes.DuplicateEmail>
| Message<MessageTypes.Success>;
Let's substitute Message
and ContentMapping
type Option1 = {
type: MessageTypes.DuplicateEmail | MessageTypes.Success;
content:
| {
data: any;
}
| {
email: string;
};
};
type Option2 =
| {
type: MessageTypes.DuplicateEmail;
content: {
email: string;
};
}
| {
type: MessageTypes.Success;
content: {
data: any;
};
};
In Option1
, type
and content
are not connected.
A malformed Message
can be created:
const message: Option1 = {
type: MessageTypes.DuplicateEmail,
content: {
data: "This should not be allowed!",
},
};
On the other hand, Option2
throws an error:
const message2: Option2 = {
type: MessageTypes.DuplicateEmail,
content: {
data: "This *is* not allowed!",
},
};
Property 'email' is missing in type '{ data: string; }' but required in type '{ email: string; }'.
Check out this TypeScript playground for a live demo.