So definieren Sie in TypeScript einen Typ, bei dem es sich um eine Zeichenfolge handelt, die nur Wörter aus einer vordefinierten Liste enthalten kann
P粉139351297
P粉139351297 2024-01-10 17:24:00
0
1
517

Ich habe ein kniffliges TypeScript-Problem.

Angenommen, ich habe eine Symbolkomponente mit Requisitengröße. Die Größe kann „2“, „4“, „6“ sein. Ich ordne diese Werte einer vordefinierten Rückenwindklasse zu.

Also tippe ich so

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" />

Alles ist in Ordnung. Aber was ist, wenn ich je nach Bildschirmgröße unterschiedliche Größen haben möchte? Deshalb möchte ich versuchen, eine gute Grammatik zu haben, die reibungslos funktioniert.

Also habe ich die Icon-Komponente wie folgt umgeschrieben:

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" />

Das funktioniert super, aber wie gebe ich es ein? Ich habe gelesen, dass TypeScript in Zukunft reguläre Ausdrücke unterstützen wird. Das wird die Sache einfacher machen, aber kann ich das jetzt eingeben?

Dies ist keine echte Komponente, also machen Sie mir bitte keine guten Vorschläge, wie ich sie verbessern kann. Ich frage mich nur, wie ich mein Größenattribut eingeben soll, damit es so funktioniert, wie ich es codiert habe.

P粉139351297
P粉139351297

Antworte allen(1)
P粉509383150

首先,我们需要将 sizeMap 提取到全局范围内,并且 const assert 让编译器知道这是不可变常量并限制它扩大类型:

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;

接下来,我们需要获取 sizeMap 的键的类型:

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

实施: 我们将创建一个接受字符串的类型,如果字符串有效则返回该字符串;否则,返回never

伪代码:

让类型接受T - 要验证的字符串,Original - 原始字符串,AlreadyUsed - 已使用键的并集。

如果T是空字符串

  • 返回原始 否则,如果 T 以大小映射 (ClassName) 的键开头,不包括 AlreadyUsed,后跟一个空格和剩余的字符串(休息)。

  • 递归调用此类型,将 Rest 作为字符串传递以验证 Original,并将 AlreadyUsedClassName 添加到其中。

Else if T 是尺寸映射的键,不包括 AlreadyUsed

  • 返回原始 否则
  • 返回从不

实现:

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;

我们必须向 Item 添加一个通用参数来表示大小

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

由于 size 在组件中是可选的,因此我们将在 SizeValue 周围添加一个包装器,它将把 string | undefinedstring 并将其传递给 _SizeValue,此外我们将为大小添加一个默认值:

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

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

用法:

<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" />;

游乐场

Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage