How to define a type in TypeScript that is a string that can only contain words from a predefined list
P粉139351297
P粉139351297 2024-01-10 17:24:00
0
1
505

I have a tricky TypeScript problem.

Suppose I have an icon component with prop size. Size can be "2", "4", "6". I map these values ​​to a predefined tailwind class.

So I typed like this

type SizeValues = '2' | '4' | '6';

function Icon({size = '4'}: {size: SizeValues}) {
   const sizeMap = {
     '2': 'w-2 h-2',
     '4': 'w-4 h-4',
     '6': 'w-6 h-6',
   };
 
   return <span className={sizeMap[size]}>My icon goes here</span>
}

<Icon size="sm" />

all is well. But what if I want to have different sizes based on my screen size? So I want to try to have good grammar that goes smoothly.

So I rewrote the Icon component to the following:

type SizeValues = ???

function Icon({size = '4'}: {size: SizeValues}) {
   const sizeMap = {
     '2': 'w-2 h-2',
     '4': 'w-4 h-4',
     '6': 'w-6 h-6',
     'md:2': 'md:w-2 md:h-2',
     'md:4': 'md:w-4 md:h-4',
     'md:6': 'md:w-6 md:h-6',
     'lg:2': 'lg:w-2 lg:h-2',
     'lg:4': 'lg:w-4 lg:h-4',
     'lg:6': 'lg:w-6 lg:h-6',
   };
 
   return <span className={size.split(' ').map(s => sizeMap[s]).join(' ').trim()}>My icon goes here</span>
}

<Icon size="2 md:4 lg:6" />

This works great, but how do I enter it? I read that TypeScript will support regular expressions in the future. This will make things easier, but can I type this now?

This is not a real component, so please don't give me good suggestions on how to improve it. I'm just wondering how to input my size attribute so that it works the way I coded it.

P粉139351297
P粉139351297

reply all(1)
P粉509383150

First, we need to extract the sizeMap into the global scope and const assert to let the compiler know that this is an immutable constant and restrict it from expanding the type:

const sizeMap = {
  '2': 'w-2 h-2',
  '4': 'w-4 h-4',
  '6': 'w-6 h-6',
  'md:2': 'md:w-2 md:h-2',
  'md:4': 'md:w-4 md:h-4',
  'md:6': 'md:w-6 md:h-6',
  'lg:2': 'lg:w-2 lg:h-2',
  'lg:4': 'lg:w-4 lg:h-4',
  'lg:6': 'lg:w-6 lg:h-6',
} as const;

Next, we need to get the type of key of sizeMap:

type SizeMap = typeof sizeMap;
type SizeMapKeys = keyof SizeMap;

Implementation: We'll create a type that accepts a string and returns it if the string is valid; otherwise, never is returned.

pseudocode:

Let the type accept T - the string to be validated, Original - the original string, AlreadyUsed - the union of used keys.

If T is an empty string

  • Returnoriginal Otherwise, if T begins with the key of the size map (ClassName), excluding AlreadyUsed, followed by a space and the remaining string (break).

  • Call this type recursively, passing Rest as a string to validate Original, and AlreadyUsed with ClassName added to it.

Else if T is the key of the size map, excluding AlreadyUsed

  • Returnoriginal otherwise
  • ReturnNever

accomplish:

type _SizeValue<
  T extends string,
  Original extends string = T,
  AlreadyUsed extends string = never
> = T extends ""
  ? Original
  : T extends `${infer ClassName extends Exclude<
      SizeMapKeys,
      AlreadyUsed
    >} ${infer Rest extends string}`
  ? _SizeValue<Rest, Original, AlreadyUsed | ClassName>
  : T extends Exclude<SizeMapKeys, AlreadyUsed>
  ? Original
  : never;

We must add a common parameter to the Item to represent the size.

function Icon<T extends string | undefined>({
  size,
}: {
  size: _SizeValue<T>;
}) {
  return null;
}

Since size is optional in the component, we will add a wrapper around SizeValue which will convert string | undefined to string and pass it to _SizeValue, in addition we will add a default value for the size:

type SizeValue<T extends string | undefined> = _SizeValue<NonNullable<T>>;

function Icon<T extends string | undefined>({
  size = "2",
}: {
  size?: SizeValue<T> | "2";
}) {
  return null;
}

usage:

<Icon size="2" />;
<Icon size="md:2" />;
<Icon size="md:2 md:6" />;
<Icon size="md:2 md:6 lg:6" />;

// expected error
<Icon size="md:2 md:6 lg:5" />;

// no duplicates allowed
<Icon size="2 2" />;

Playground

Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template