Heim > Fragen und Antworten > Hauptteil
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粉5093831502024-01-11 10:57:21
首先,我们需要将 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
,并将 AlreadyUsed
与 ClassName< /code> 添加到其中。
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 | undefined
到 string
并将其传递给 _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" />;