【问题标题】:getting a type for the values of a string enum获取字符串枚举值的类型
【发布时间】:2018-11-25 19:08:55
【问题描述】:

我有一个字符串枚举,如下所示:

export enum FMEvents {
  RECORD_ADDED = "@firemodel/RECORD_ADDED",
  RECORD_CHANGED = "@firemodel/RECORD_CHANGED",
  RECORD_MOVED = "@firemodel/RECORD_MOVED",
  RECORD_REMOVED = "@firemodel/RECORD_REMOVED",
}

我希望能够将函数的输入限制为枚举的字符串值(例如,“@firemodel/RECORD_ADDED”等)。

我想我可以只为方法签名执行以下操作:

public doSomething(event: keyof FMEvents) { ... }

但是打字都是错误的(我认为它给了我枚举对象的键,不确定但肯定是错误的)。

然后我尝试了:

public doSomething(event: FMEvents) { ... }

这允许我用FMEvents.RECORD_CHANGED 调用doSomething(),但它不允许我用doSomething("@firemodel/RECORD_CHANGED") 的解析键调用它。

我正在寻找一种将其限制为定义为枚举中值的字符串的方法,仅此而已。有了这个,我希望上面的两个调用方法都能通过类型检查。

【问题讨论】:

    标签: typescript


    【解决方案1】:

    TypeScript 4.1 引入了template literal types,其中包括将枚举转换为其字符串表示形式。因此,您的目标可以简单地实现为:

    function doSomething(event: `${FMEvents}`) { }
    
    doSomething("@firemodel/RECORD_CHANGED"); // okay
    doSomething(FMEvents.RECORD_MOVED); // still okay
    

    Playground link to code


    TS4.1 之前的答案:

    TypeScript 无法轻松地将枚举值类型扩展为从中派生的字符串或数字文字。 (有一个complication that prevents using intersections to help with this)你可以用conditional types得到相当接近你想要的:

    type Extractable<T, U> = T extends U ? any : never
    type NotString<T> = string extends T ? never : any
    function promoteStringToFMEvents<K 
      extends string & NotString<K> & Extractable<FMEvents, K>>(
      k: K
    ): Extract<FMEvents, K> {
      return k;
    }
    
    const fmAdded = promoteStringToFMEvents("@firemodel/RECORD_ADDED"); // FMEvents.RECORD_ADDED
    const fmOops = promoteStringToFMEvents("@firemodel/RECORD_ADDLED"); // error
    

    在上述代码中,如果T 或其任何组成部分可分配给U,则Extractable&lt;T, U&gt; 返回any,否则返回never。并且NotString&lt;T&gt; 返回anyT 不是string 或更宽,否则never。通过将promoteStringToFMEvents() 中的K 约束为string &amp; NotString&lt;K&gt; &amp; Extractable&lt;FMEvents, K&gt;,我们说类型参数K 必须是某个字符串文字(或字符串文字的联合),FMEvents 的某些元素(或元素的联合)可以被分配到。

    所以函数promoteStringToFMEvents() 将接受您期望的字符串文字(或字符串文字的联合)。该函数还通过将输入值分配给Extract&lt;FMEvents, K&gt; 来返回FMEvents 的关联元素,它只提取与K 匹配的FMEvents 片段。

    因此,您可以编写 doSomething() 方法,使其在上面的 K 类型中是通用的,并且在该方法的实现中,您可以(如果需要)通过分配字符串将其提升为枚举到 Extract&lt;FMEvents, K&gt; 类型的变量。

    使用doSomething() 的显式实现进行编辑:

    class Blomp {
      public doSomething<K
        extends string & NotString<K> & Extractable<FMEvents, K>>(k: K) {
        // k is of some subtype of "@firemodel/RECORD_ADDED" | 
        // "@firemodel/RECORD_CHANGED" | "@firemodel/RECORD_MOVED" |
        // "@firemodel/RECORD_REMOVED"
    
        // if you need to interpret k as a subtype of FMEvents, you can:
        const kAsFMEvent: Extract<FMEvents, K> = k;
    
        // or even wider as just FMEvents
        const fmEvent: FMEvents = kAsFMEvent;
    
        // do what you want here
      }
    }
    

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

    【讨论】:

    • 哇,这很复杂。我想我错过了一个关键字而不是几段。非常感谢。出去吃个炸玉米饼,但我一回来就试试。
    • 我的头还在转……我如何获得与我的 doSomething 方法关联的 K 类型?我认为在这一点上我不需要像 demote 那样进行宣传。如果这有意义的话。
    • 我刚刚用doSomething() 进行了编辑,接受了K 类型,该类型受限于与FMEvents 关联的字符串文字类型。
    • 一切正常。非常感谢。我剩下的一个问题......有没有一种方法可以将这个逻辑封装在一个地方,以便具有这种类型的函数有一种更简写的表达方式?我试过export type IFMEvent&lt;K&gt; = K extends string &amp; NotString&lt;K&gt; &amp; Extractable&lt;FMEvents, K&gt;;,但它抱怨“导出的类型别名'IFMEvent'已经或正在使用私有名称''。”
    • 您应该可以这样做,但是“使用私人名称”消息很奇怪;您应该确保导出所有引用的名称(例如NotStringExtractable,看看它是否会变得更好。
    【解决方案2】:

    template literal 运算符的帮助下,可以将枚举的值列表推断为类型:

    export enum FMEvents {
      RECORD_ADDED = "@firemodel/RECORD_ADDED",
      RECORD_CHANGED = "@firemodel/RECORD_CHANGED",
      RECORD_MOVED = "@firemodel/RECORD_MOVED",
      RECORD_REMOVED = "@firemodel/RECORD_REMOVED",
    }
    
    type FMEventsValue = `${FMEvents}`
    // => type FMEventsValue = "@firemodel/RECORD_ADDED" | "@firemodel/RECORD_CHANGED" | ...
    
    const event: FMEventsValue = "@firemodel/RECORD_ADDED"
    // => ✅ OK
    
    const event: FMEventsValue = "NoT_iN_ThE_eNuM"
    // => ? KO
    

    参考文章:Get the values of an enum dynamically (免责声明:作者在此)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-23
      • 1970-01-01
      • 2018-02-14
      • 2020-12-19
      相关资源
      最近更新 更多