【问题标题】:Parse string as Typescript Enum将字符串解析为 Typescript 枚举
【发布时间】:2018-09-17 14:56:36
【问题描述】:

给定一个如下所示的枚举:

export enum UsedProduct {
    Yes = 'yes',
    No = 'no',
    Unknown = 'unknown',
}

我想编写一个函数,它接受一组字符串文字并返回UsedProduct 的一个实例。到目前为止,我写了一个这样的函数:

export function parseUsedProduct(usedProdStr: 'yes' | 'no' | 'unknown'): UsedProduct {
    switch (usedProdStr) {
        case 'yes':
            return UsedProduct.Yes;
        case 'no':
            return UsedProduct.No;
        case 'unknown':
            return UsedProduct.Unknown;
        default:
            return unknownUsedProductValue(usedProdStr);
    }
}

function unknownUsedProductValue(usedProdStr: never): UsedProduct {
    throw new Error(`Unhandled UsedProduct value found ${usedProdStr}`);
}

这个实现不是很好,因为我必须重新定义枚举的可能值。我怎样才能重写这个函数,这样我就不必定义'yes' | 'no' | 'unknown'

【问题讨论】:

    标签: typescript enums


    【解决方案1】:

    TS4.1 ANSWER:

    type UsedProductType = `${UsedProduct}`;
    

    TS-4.1 之前的答案:

    TypeScript 对您来说并不容易,所以答案不是单行的。

    UsedProduct.Yes 这样的enum 值在运行时只是一个字符串或数字文字(在本例中为字符串"yes"),但在编译时它被视为一个子类型的字符串或数字文字。所以,UsedProduct.Yes extends "yes" 是真的。不幸的是,给定类型UsedProduct.Yes,没有编程方法可以将类型扩展为"yes"...,或者,给定类型UsedProduct,没有编程方法可以将其扩展为"yes" | "no" | "unknown"。该语言缺少一些您需要执行此操作的功能。

    一种方法可以创建一个函数签名,其行为类似于parseUsedProduct,但它使用genericsconditional types 来实现此目的:

    type Not<T> = [T] extends [never] ? unknown : never
    type Extractable<T, U> = Not<U extends any ? Not<T extends U ? unknown : never> : never>
    
    declare function asEnum<E extends Record<keyof E, string | number>, K extends string | number>(
      e: E, k: K & Extractable<E[keyof E], K>
    ): Extract<E[keyof E], K>
    
    const yes = asEnum(UsedProduct, "yes"); // UsedProduct.yes
    const no = asEnum(UsedProduct, "no"); // UsedProduct.no
    const unknown = asEnum(UsedProduct, "unknown"); // UsedProduct.unknown
    const yesOrNo = asEnum(UsedProduct, 
      Math.random()<0.5 ? "yes" : "no"); // UsedProduct.yes | UsedProduct.no
    
    const unacceptable = asEnum(UsedProduct, "oops"); // error
    

    基本上,它采用枚举对象类型E 和字符串或数字类型K,并尝试提取扩展KE 的属性值。如果没有E 的值扩展K(或者如果K 是联合类型,其中一个部分不对应于E 的任何值),编译器将给出错误。 Not&lt;&gt;Extractable&lt;&gt; 的具体工作方式可应要求提供。

    至于函数的实现,您可能需要使用type assertion。比如:

    function asEnum<E extends Record<keyof E, string | number>, K extends string | number>(
      e: E, k: K & Extractable<E[keyof E], K>
    ): Extract<E[keyof E], K> {
      // runtime guard, shouldn't need it at compiler time
      if (Object.values(e).indexOf(k) < 0)
        throw new Error("Expected one of " + Object.values(e).join(", "));
      return k as any; // assertion
    }
    

    应该可以。在您的具体情况下,我们可以硬编码UsedProduct:

    type Not<T> = [T] extends [never] ? unknown : never
    type Extractable<T, U> = Not<U extends any ? Not<T extends U ? unknown : never> : never>
    function parseUsedProduct<K extends string | number>(
      k: K & Extractable<UsedProduct, K>
    ): Extract<UsedProduct, K> {
      if (Object.values(UsedProduct).indexOf(k) < 0)
        throw new Error("Expected one of " + Object.values(UsedProduct).join(", "));
      return k as any;
    }
    
    const yes = parseUsedProduct("yes"); // UsedProduct.yes
    const unacceptable = parseUsedProduct("oops"); // error
    

    希望对您有所帮助。祝你好运!

    【讨论】:

      【解决方案2】:

      您可以使用 ts-enum-util 中的getKeyOrThrow-方法。不知道它是如何实现的,但你可以看看它here

      Here's a stackblitz我是为了演示你的情况下的用法。

      【讨论】:

      • 我可能太天真了,但我不太明白这是如何回答这个问题的;问题想知道如何在函数调用中使用枚举的值代替字符串文字。您能否在答案中添加一些内容以演示其工作原理?
      • 看起来该库希望枚举具有数值而不是字符串值。 stackblitz 显示编译器错误。
      • @OliverRadini 不确定我是否理解这个问题。在这种情况下,枚举的值是字符串文字,OP 似乎想要一个函数通过将值作为输入来获取枚举键。这就是getKeyOrThrow 所做的。当然,您不会通过单独定义可能的输入来获得类型安全,也许这也是 OP 想要的。
      • @jcalz 是的,但是文档显示了如何对字符串值进行操作,所以我认为这只是库中的类型定义错误。您可以查看他们的代码并自己实现,因为代码有效。
      【解决方案3】:

      使用Typescript 4.1,可以更简单的方式完成

      type UnionToEnum<E extends string, U extends `${E}`> = {
        [enumValue in E as `${enumValue & string}`]: enumValue
      }[U]
      
      enum UsedProduct {
        Yes = 'yes',
        No = 'no',
        Unknown = 'unknown',
      }
      
      function parseUsedProduct<K extends `${UsedProduct}`>(k: K): UnionToEnum<UsedProduct, K> {
        if (Object.values(UsedProduct).indexOf(k as UsedProduct) < 0)
          throw new Error("Expected one of " + Object.values(UsedProduct).join(", "));
        return k as UsedProduct as UnionToEnum<UsedProduct, K>;
      }
      
      // x is of type UsedProduct.Yes
      let x = parseUsedProduct('yes');
      // error
      let c = parseUsedProduct('noo');
      
      

      playground

      这里的关键是`${UsedProduct}`,它去除了枚举值的“枚举”,并将它们转换为字符串文字。

      警告:这只适用于字符串枚举值,不适用于数字枚举值。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-25
        • 1970-01-01
        • 1970-01-01
        • 2019-03-10
        • 1970-01-01
        相关资源
        最近更新 更多