I have a strongly-typed style object in my React Native project in TypeScript:
const styles = StyleSheet.create({
container:{
backgroundColor: 'red',
flex: 1
},
});
This works as normal as StyleSheet.create
expects arbitrary named keys with the target values of ViewStyle
, TextStyle
, and ImageStyle
as defined below:
export namespace StyleSheet {
type NamedStyles<T> = { [P in keyof T]: ViewStyle | TextStyle | ImageStyle };
/**
* Creates a StyleSheet style reference from the given object.
*/
export function create<T extends NamedStyles<T> | NamedStyles<any>>(styles: T | NamedStyles<T>): T;
[...]
}
container
is an arbitrary key, and backgroundColor
is defined in ViewStyle
to have a value of the type ColorValue
, which is defined as string | OpaqueColorValue
where OpaqueColorValue
is a unique symbol.
However, I'm building a function that I want to feed with the following type signature wherever ColorValue
was previously accepted in original type:
ColorValue | { light: ColorValue, dark: ColorValue }
Along with a parameter named either light
or dark
supplied to my function, I will use it like:
const styles = myFunction({
container:{
backgroundColor: {
light: 'red',
dark: 'blue'
},
flex: 1
},
}, 'dark');
And it will pick the appropriate key, e.g. it will return:
{
container:{
backgroundColor: 'blue' //as we picked the 'dark' key from original input
flex: 1
}
}
In plain English, I want my function's input type to accept anything that StyleSheet.create
accepts, and in addition to that, also accept {light: ColorValue, dark: ColorValue}
wherever it accepted ColorValue
. It then picks the appropriate keys, then calls StyleSheet.create
internally and returns the result.
I wrote the function and it works, but I'm struggling to make it accept objects with the light/dark
keys in IntelliSense and linter (of course, without casting to any
or unknown
).
How do I achieve this?
I think you might benefit from having a few conditional, mapped types like this:
The idea is that
DeepReplaceSupertype<T, S, D>
takes a typeT
, a source typeS
, and a destination typeD
and it checks: ifS
is assignable toT
, then replace it withD
. Otherwise, walk down recursively throughT
and replace all properties and subproperties the same way.DeepReplaceSubtype<T, S, D>
is similar but it checks ifT
is assignable toS
.Then your function's signature could look like this:
Meaning: accept anything of type
T
where you take thatNameStyles
and replace subproperties that acceptColorValue
and change them toShade
. And then, the return value should takeT
and replace any subproperties that are a subtype ofShade
and turn them back toColorValue
. It should work like this:Looks like what you want, I think. You might be able to get even more clever and have the output type be specifically the type of the value picked from
light
ordark
(sostring
instead ofColorValue
), but that would be more complicated and probably not necessary, so I'll stop there.Playground link to code