如何在TypeScript严格模式下查询字符串枚举?

时间:2018-07-13 11:45:39

标签: typescript typescript-typings

严格模式下的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]));

稍后,我试图使用查询参数从该地图中获取特定成员,该参数应该在地图中可用:

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"'

2 个答案:

答案 0 :(得分:2)

我认为问题在于您要给地图指定类型: Map<string, string>。这是由您创建地图的方式引起的:new Map(<[string, string][]> ...)

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

在下一个代码段中,您尝试通过使用键调用get方法来使用该Map的值。但是,这会返回一个字符串(因为您地图的类型已定义为具有字符串类型的值。

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

但是,TabIndex[xxxx]语句期望'xxxx'是以下值"Editor" | "Console" | "Settings" | "Outputs"之一,您可以从上面的错误消息中扣除。

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

keyof typeof TabIndex

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

总而言之,将地图的创建更改为:

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

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

答案 1 :(得分:0)

问题在于,在定义typeParam的行中,您要分配一个string(我想象中的queryParam.tab是字符串?或者是anykeyof 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并不是真正的<string, number>地图,因此我们在撒谎。实际上,它具有字符串和数字。数字键指向属性名称,字符串键指向它们的数字等效项。但是,我们会忘记它,因为您将仅使用字符串键。

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

希望这对您有所帮助。