'How to describe an object with alternative structures / entries in TypeScript
I'm using TypeScript to work with animation data, and I'm trying to create a Keyframe interface (or some other way to tell TypeScript about the shape of a keyframe).
A Keyframe might be one of three different configurations:
- those animating only transform properties
- those animating only non-transform properties
- those mixing both.
Keyframes might look like this:
const transformKeyframe = {
'easing': { 'translateX': 'easeInOutQuad' },
'transform': { 'translateX': '10px' }
}
const propertyKeyframe = {
'easing': { 'opacity': 'linear' },
'opacity': '0.5'
}
const mixedKeyframe = {
'easing': {
'translateX': 'easeInOutQuad',
'translateY': 'easeInOutQuad',
'opacity:': 'linear',
'fill:': 'linear'
},
'transform': {
'translateX': '10px',
'translateY': '10px',
},
'opacity:': '0.5',
'fill:': 'red'
}
const finalKeyframe = {
{
'easing': { },
'opacity': '0.5'
}
}
A Keyframe:
always has an
easingobject, which will either be empty, or contain[key: string]: stringentries. These keys will always be one of the ~200 animatable CSS properties, in camelCase.sometimes has a
transformobject, containing[key: string]: stringentries. These keys will always be one of the 18 CSS transform properties, in camelCase.sometimes has
[property]key:value pairs. These keys will always be one of the ~200 animatable CSS properties (excluding the 18 transform properties), and will never be the strings'easing'or'transform'always has either the
transformobject, or at least one[property]key:value pair.
What I have been trying is something like this:
interface KeyFrame {
easing: Record<string, string>;
transform?: Record<string, string>;
[property: string]: string;
}
One apparent problem is that because 'easing' and 'transform' fit the pattern of being string keys, they are interpreted as an instance of [property: string]: string;, which causes an error because their values are not of type string. At least, I think that's what's happening..?
I'm new to Typescript, and may be missing something obvious, but I can't find the answer to this in the handbook.
Solution 1:[1]
As far as I know, this isn't really possible with index signatures, by defining [property: string]: string, TS is really expecting all values to match. The workaround is a union [property: string]: string | Record<string, string>, but this is very often not what is desired.
TypeScript comes with a built-in type called CSSStyleDeclaration which will have all the keys you want, I think...
We'll intersect all the possible keys, with our own easing and transform values. I couldn't find a similar built-in list for transform values, so we'll keep that Record<string, string>
type KeyFrame = Partial<CSSStyleDeclaration> & {
easing: Partial<CSSStyleDeclaration>
transform?: Record<string, string>
}
We have to use a type alias, because CSSStyleDeclaration already has a transform key/value configured, but it's set to string. So we can overwrite using a type alias and intersection
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Cody Duong |
