Hi there i have a function called createBreakpointValue
that takes an Object of arguments: desktop, tablet, mobile, allElse
The type-logic i am requiring is:
If all are passed (desktop, tablet, mobile
), then allElse
is irrelevant and should error if allElse
is passed.
If some are passed (mobile
), then allElse
should be required or desktop & tablet
.
What i currently have:
type ValueOf<T> = T[keyof T];
type Points =
| {
desktop: any;
tablet: any;
mobile: any;
allElse?: never;
}
| {
desktop?: any;
tablet?: any;
mobile?: any;
allElse: any;
};
const currentViewport: keyof Points = "desktop";
const createBreakpointValue = (points: Points): ValueOf<Points> => {
if (currentViewport in points) {
return points[currentViewport];
} else {
return points.allElse;
}
};
// correct
createBreakpointValue({ allElse: 1 });
//correct
createBreakpointValue({ desktop: 1, tablet: 1, mobile: 1 });
// should highlight allElse as incorrect
createBreakpointValue({ allElse: 1, desktop: 1, mobile: 1, tablet: 1 });
// should highlight saying allElse or mobile should be provided
createBreakpointValue({ desktop: 1, tablet: 1 });
At the moment the typing is correct until i pass all correct arguments as well as allElse which i would expect to be highlighted saying something like "allElse is not valid here"
Sandbox: https://codesandbox.io/s/lucid-breeze-zlgws0?file=/src/index.ts
CodePudding user response:
Th problem is that the value passed in is still compatible with the last member of your union (the one with all members optional)
You could create a union where a member with all properties is incompatible iwth the union, by having the union expand with variants where at least one property must be undefined
:
type Points =
| {
desktop?: undefined
tablet?: any
mobile?: any
allElse?: any
}
| {
tablet?: undefined
desktop?: any
mobile?: any
allElse?: any
}
| {
mobile?: undefined
desktop?: any
tablet?: any
allElse?: any
}
| {
allElse?: undefined
desktop?: any
tablet?: any
mobile?: any
}
You can probably also generate such a type using mapped types:
type NotAll<T> = {
[P in keyof T]: Partial<Record<P, undefined>> & Omit<T, P>
}[keyof T]
type Points = NotAll<{
desktop?: any
tablet?: any
mobile?: any
allElse?: any
}>
CodePudding user response:
I like enums
enum ViewPort {
desktop = 1 << 1,
tablet = 1 << 2,
mobile = 1 << 3,
allElse = desktop | tablet | mobile
}
const currentViewport: ViewPort = ViewPort.desktop;
const createBreakpointValue = (points: ViewPort) => {
return (points & currentViewport) === currentViewport;
};