In the following setup:
export interface BaseMenuItem { title: string; icon: string; }
export interface ComponentMenuItem extends BaseMenuItem { component: any; }
export interface ClickMenuItem extends BaseMenuItem { click: any; }
export type MenuItem = ComponentMenuItem | ClickMenuItem;
const myMenuItem: MenuItem = { title: 'blah', icon: 'blah2', click: true, component: true, }
You would not get a compilation error on myMenuItem. The union allows for properties of each of the type to merge in this way.
However you could truly intend for MenuItem to be either a ComponentMenuItem or a ClickMenuItem. This would follow the line of thinking that that if you tried to instantiate an object of either type on their own without this union it would fail compilation if you added a property of the other, say ClickMenuItem type with a ‘component’ property.
The only way with still using the union I am currently aware of is the following method:
...
export interface ComponentMenuItem extends BaseMenuItem {
component: any;
click?: never; // Ensure click is not present
}
export interface ClickMenuItem extends BaseMenuItem {
click: any;
component?: never; // Ensure component is not present
}
...
However overtime with many union types or many unique properties in each of the unioned types this would cause maintenance frustrations or at the very least ugly and unclear code (never’s polluting the type definitions of each of the unioned types).
If the type was going to be used for parameters of a function of course you could just overload and make a signature for each type distinctly but that is not always the case.
Is there a more dynamic (not relying on you to manually keep updating the ‘nevers’) and cleaner way to accomplish this?