Issue
I want to accomplish that certain keys in my interface are only valid if a specific generic that can have specific values is set.
Something like
type DeviceType = "A" | "B" | "default"
interface IIconSet<DEVICE extends DeviceType> {
info: string;
download: DEVICE extends "A" ? string : never;
refresh: DEVICE extends ("A" | "B") ? string : never;
}
However if i use it like this TypeScript apparently can't properly infer what keys should be present in the interface since with the following Object
export const icons: IIconSet<"default"> {
info: "svgString"
}
i get the error: Type is missing the following properties from type IIconSet<"default">: download, refresh
. The same occurs when specifying IIconSet<"B">
(in this case i get the error that download
is missing, as soon as i add the refresh
key).
However when defining the object with interface IIconSet<"A">
it works just fine.
I tried to tinker around with making said fields optional but then, when i want to get that iconset out of a factory e.g. with typecasting to IIconSet<"A">
i get an error, that download
could potentially undefined.
What would be the proper solution to accomplish this -> excluding keys when the generic passed doesn't fit to the constraints?
Solution
What you need are discriminated unions. We will create a separate type to hold every DeviceType
:
type DeviceType = 'A' | 'B' | 'default';
Now let's create a type that will hold the unique parts for every DeviceType
:
type DeviceTypeMap = {
A: {
download: string;
refresh: string;
};
B: {
refresh: string;
};
};
The only thing we need now is for you to modify the IIconSet
. To make this type more flexible I made the generic argument optional and added the conditional type distribution(will show the result later).
After distributing we get the common part which is info: string
and if the device type is in the DeviceTypeMap
we get the part from it and intersect it with the common part:
type IIconSet<T extends DeviceType = DeviceType> = T extends T
? { info: string } & (T extends keyof DeviceTypeMap ? DeviceTypeMap[T] : {})
: never;
Testing:
// type Result = {
// info: string;
// } | ({
// info: string;
// } & {
// download: string;
// refresh: string;
// }) | ({
// info: string;
// } & {
// refresh: string;
// })
type Result = IIconSet
We got the desired shape because we used the conditional distribution
Case 2:
// type Result = {
// info: string;
// } & {
// download: string;
// refresh: string;
// }
type Result = IIconSet<'A'>
Usage:
export const icons: IIconSet<'default'> = { // no error
info: 'svgString',
};
export const iconsA: IIconSet<'A'> = { // no error
info: 'info',
download: '',
refresh: 'a',
};
Answered By - wonderflame
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.