Home > OS >  How to describe an object with alternative structures / entries in TypeScript
How to describe an object with alternative structures / entries in TypeScript

Time:03-24

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 easing object, which will either be empty, or contain [key: string]: string entries. These keys will always be one of the ~200 animatable CSS properties, in camelCase.

  • sometimes has a transform object, containing [key: string]: string entries. 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 transform object, 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.

CodePudding user response:

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>
}

View this on TS Playground

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

  • Related