【问题标题】:How to query string enums in TypeScript strict mode?如何在 TypeScript 严格模式下查询字符串枚举?
【发布时间】:2018-07-13 22:29:20
【问题描述】:

TypeScript 2.8.4 处于严格模式

我有一个这样的枚举:

export enum TabIndex {
  Editor = 'editor',
  Console = 'console',
  Settings = 'settings',
  Outputs = 'outputs'
}

我正在创建这样的地图:

tabs = new Map(<[string, string][]>Object.keys(TabIndex).map((key: any) => [TabIndex[key], key]));

稍后,我尝试使用 Map 中应该可用的查询参数从该 Map 中获取特定成员:

const tabParam = this.urlSerializer.parse(this.location.path()).queryParams.tab; // this thign is either 'editor', 'console', 'settings', ... etc.

然后我做

if (this.tabs.has(tabParam)) { // at this point we know the tab is available
  this.selectTab(TabIndex[this.tabs.get(tabParam)!]); // here, TS thinks the index may be undefined, that's why I'm using the non-null assertion operator "!"
}

这段代码仍然让 TS 不高兴。它出错了:

Element implicitly has an 'any' type because index expression is not of type 'number'.

确实,索引类型是字符串。但我知道这就是它应该的样子,因为枚举支持字符串值。有人知道如何在这里让 TS 开心吗?

我做了一些研究,this issue 评论建议使用keyof typeof 解决此问题:

const tabParam: keyof typeof TabIndex = this.urlSerializer.parse(this.location.path()).queryParams.tab;

这只会让 TypeScript 再次不开心:

Type 'string' is not assignable to type '"Editor" | "Console" | "Settings" | "Outputs"'

【问题讨论】:

    标签: typescript typescript-typings


    【解决方案1】:

    我认为问题在于您为地图指定了以下类型: Map&lt;string, string&gt;。这是由您创建地图的方式引起的:new Map(&lt;[string, string][]&gt; ...)

    这导致您遇到的错误: Type 'string' is not assignable to type '"Editor" | "Console" | "Settings" | "Outputs"'

    在下一个 sn-p 中,您尝试通过使用键调用 get 方法来使用该 Map 的值。然而,这会返回一个字符串(因为您的地图类型已被定义为具有字符串类型的值。

    this.selectTab(TabIndex[this.tabs.get(tabParam)!]);

    但是,TabIndex[xxxx] 语句预计“xxxx”是以下值之一 "Editor" | "Console" | "Settings" | "Outputs",您可以从上面的错误消息中推断出来。

    要解决这个问题,您需要更改 Map 的类型,以便打字稿知道上面 sn-p 中的“xxxx”将始终是这些值之一。为此,您需要创建一个“这些特定字符串文字的联合类型”。幸运的是,typescript 为我们提供了一种从 TabIndex 定义中提取它们的方法。

    keyof typeof TabIndex

    这将解析为正确键入 Map 所需的“字符串文字的联合类型”。

    所以总结一下,把地图的创建改成:

    const tabs = new Map(&lt;[string, keyof typeof TabIndex][]&gt;Object.keys(TabIndex).map((key: any) =&gt; [TabIndex[key], key]));

    这将确保 Map 中可以传递给 TabIndex[xxxx] 的值始终是已知的字符串文字。

    【讨论】:

      【解决方案2】:

      问题在于,在定义typeParam 的行中,您将stringqueryParam.tab 是我想象的字符串?要么是那个,要么是any)分配给keyof typeof TabIndexstring 可以包含任何字符串值,因此不能将其分配给只接受四个特定值的变量。

      但是,如果您确定 tab 将始终如您所愿,您可以做出断言:

      type TabIndexKey = keyof typeof TabIndex;
      const tabParam: TabIndexKey = this.urlSerializer.parse(this.location.path()).queryParams.tab as TabIndexKey.
      

      如果queryParams.tabany,则解决方法相同。

      编辑: 为了避免非隐式任何错误,枚举的索引表达式必须使用数字,而不是字符串。这意味着:

      TabIndex['editor']; // error with not implicit any
      TabIndex[0]; // correct
      

      所以,我建议您更改枚举和 tabs 映射的定义。不要分配字符串,而是分配数字:

      export enum TabIndex {
        Editor = 0,
        Console = 1,
        Settings = 2,
        Outputs = 3
      }
      
      const tabs = new Map(<[string, number][]>Object.keys(TabIndex).map((key: any) => [key, TabIndex[key]]));
      

      当然,这样做我们有点撒谎,因为tabs 并不是真正的&lt;string, number&gt; 地图。实际上它既有字符串又有数字。数字键指向属性名称,字符串键指向它们的等价物。但是,我们可以忘记它,因为您将只使用字符串键。

      现在,TabIndex[this.tabs.get(tabParam)!] 可以正常工作,因为this.tabs.get 返回一个number

      希望对你有所帮助。

      【讨论】:

      • 这仍然给我留下了:TS7015:元素隐式具有“任何”类型,因为索引表达式不是“数字”类型。 for this.selectTab(TabIndex[this.tabs.get(tabParam)!]);
      • 编辑了我的答案
      • 所以没有办法用字符串值来保持枚举对吗?这很不幸,因为那时我将不得不介绍另一个映射,它将索引映射到实际的字符串值,以保持通过查询参数控制所选选项卡的功能......
      猜你喜欢
      • 2019-03-23
      • 2019-03-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-27
      • 1970-01-01
      • 1970-01-01
      • 2019-03-10
      相关资源
      最近更新 更多