我有一个棘手的 TypeScript 问题。
假设我有一个具有道具大小的图标组件。大小可以是“2”、“4”、“6”。我将这些值映射到预定义的顺风类。
所以我这样输入
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" />
一切都很好。但是,如果我想根据我的屏幕尺寸有不同的尺寸怎么办?所以我想尝试拥有顺风顺水的良好语法。
因此,我将 Icon 组件重写为以下内容:
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" />
这很好用,但是我该如何输入呢?我读到 TypeScript 将来会支持正则表达式。这会让事情变得更容易,但是现在可以输入这个吗?
这不是一个真正的组件,所以请不要给我如何改进它的好建议。我只是想知道如何输入我的 size 属性,以便它按照我编码的方式工作。
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" />;