I have a function called myStyles
, which takes a single argument. This argument is an object containing a baseStyles
object and a styleVariants
object. It simply returns the styleVariants
object.
function myStyles<StyleVariants extends StyleVariantsStructure>(params: {
baseStyles: AllowedStyles;
styleVariants: StyleVariants;
}): StyleVariants {
return params.styleVariants;
}
It is called like so:
myStyles({
baseStyles: {
color: 'red',
backgroundColor: 'red',
},
styleVariants: {
color: {
red: {
color: 'red',
backgroundColor: 'red',
},
},
},
});
The baseStyles
object has the type AllowedStyles
, which determines which css properties and values we allow. This works perfectly and we get the correct TS errors if the CSS properties or values are misspelled when calling the myStyles
function eg
type AllowedStyles = {
color?: 'red';
backgroundColor?: 'red';
};
The styleVariants
object has the type StyleVariantsStructure
. This essentially determines that it should accept one or many nested objects that contain the AllowedStyles
eg
type StyleVariantsStructure = {
[k: string]: {
[k: string]: AllowedStyles;
};
};
In the myStyles
function, we use type inference for the styleVariants
param object. Meaning, we automatically get the exact type of the styleVariants
passed in to the myStyles
function. We then use the styleVariants
type as the return type for the function. This is required for us, because we need to know the exact return type of myStyles
. Here's our function again:
function myStyles<StyleVariants extends StyleVariantsStructure>(params: {
baseStyles: AllowedStyles;
styleVariants: StyleVariants;
}): StyleVariants {
return params.styleVariants;
}
And here's an example of the type inference return type, taken from a Typescript playground I've used to demo this issue :
The problem
As you can see, I need to both restrict things being passed in the styleVariants
object, using the StyleVariantsStructure
type eg we must only accept AllowedStyles
. However, we also need to use the inferred type from styleVariants
for the return type of the myStyles
function. I have attempted to solve this by using StyleVariants extends StyleVariantsStructure
. This works somewhat, but some Typescript errors aren't highlighted in the styleVariants
argument object when calling myStyles
.
For example, if color
is misspelled, we get no TS error:
However, I do get type suggestions:
And, I do get errors if the value is misspelled:
Essentially, to me, it seems like I need to find an alternate way to use type inference for the styleVariants
object, whilst also restricting it using the StyleVariantsStructure
type. Any help on this would be much appreciated.
Strictly speaking, this looks correct, but TS needs to be coaxed into checking it. For example, wrapping the call in the playground with console.log() generates the typechecking you want.
TS is imperfect as a checker, so it can take some finagling for it to detect the "right" solution in highly complex situations - which this is!
The problem seems to be a limitation of Typescript more than anything else. A workaround, which is a complete solution for us, that I found is the following:
function myStyles<StyleVariants>(params: {
baseStyles: AllowedStyles;
styleVariantKeys: StyleVariants;
styleVariants: StyleVariantsStructure;
}): StyleVariants {
return params.styleVariantKeys;
}
styleVariantsKey
object to the myStyles
function paramsstyleVariants
the StyleVariantsStructure
typeThis allows you to get type checking as intended, and allows you to return the inferred type from styleVariantsKey
. But of course it means you have to have two of the same object, which in our case isn't too much of a problem because we don't actually need the nested styles object to be returned by myStyles
, just the variant names. So our function call looks like this:
myStyles({
baseStyles: {
color: 'red',
backgroundColor: 'red',
},
styleVariantKeys: {
color: ["red"],
},
styleVariants: {
color: {
red: {
color: 'red',
backgroundColor: 'red',
},
},
},
});
So, I spend more time on it then i expect, but here we are. If you need to highlighting typo, you should based StyleVariants on AllowedStyles, for examlpe:
type AllowedStyles = {
color: 'red' | 'green';
backgroundColor: 'red';
};
type StyleVariants = {
[K in keyof AllowedStyles]?: AllowedStyles[K] | Partial<Record<AllowedStyles[K], Partial<AllowedStyles>>>
}
type StyleVariantsStructure = {
baseStyles: AllowedStyles
styleVariants: StyleVariants
}
function myStyles(params: StyleVariantsStructure):StyleVariants {
return params.styleVariants;
}
StyleVariants based on AllowedStyles structure and values, it contains every property AllowedStyles has, but they are optioanl. The type of every property is provided in AllowedStyles type of property respectively OR it is an object whitch keys is each of provided types in AllowedStyles to property and its type is again AllowedStyles with all property being optional. Hope you get it!
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.