简体   繁体   中英

Typescript Bug: Extending An Inferred Type Prevents TS Linting Errors (Playground Example Provided)

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.

Minimal demo of the issue in Typescript playground

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;
}
  1. Add styleVariantsKey object to the myStyles function params
  2. Infer the type from that
  3. Give styleVariants the StyleVariantsStructure type

This 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!

Typescript playground

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM