首页  >  问答  >  正文

如何在TypeScript中定义一种类型,该类型是一个字符串,只能包含预定义列表中的单词

我有一个棘手的 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粉139351297P粉139351297305 天前468

全部回复(1)我来回复

  • P粉509383150

    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,并将 AlreadyUsedClassName< /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 | 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" />;

    游乐场

    回复
    0
  • 取消回复