首页 > 解决方案 > 如果存在的话,是否有更优雅的方式从 TypeScript 枚举中获取值?

问题描述

如果我有一个允许值的枚举,例如:

enum PackagingUnits {
  Be = 'becher',
  Bg = 'bogen',
  Bi = 'bidon'
}

我想要一个函数来传递一个字符串来获取一个枚举值,按键,所以getPackagingUnitName('Be')会返回'becher'

天真的版本是:

function getPackagingUnitName(key: string): PackagingUnits | null {
  if (PackagingUnits[key]) {
    return PackagingUnits[key]
  }
  return null
}

但是,TypeScript(版本 3.3.3 atm)不喜欢这样,并抱怨Element implicitly has an 'any' type because index expression is not of type 'number'.每个PackagingUnits[key].

我尝试了一些 if 条件的变体(例如Object.keys(PackagingUnits).includes(key),这使得 if 条件无错误,但这仍然为 return 语句提供相同的错误,return PackagingUnits[key].

return PackagingUnits[key as PackagingUnits]也不起作用。

我能想出的唯一有效语法是:

function getPackagingUnitName(key: string): PackagingUnits | null {
  const puIndex = Object.keys(PackagingUnits).indexOf(key)
  if (puIndex !== -1) {
    return Object.values(PackagingUnits)[puIndex]
  }
  return null
}

这真的是最好的方法吗?

注意:是的,如果不是枚举,这可以更容易地完成PackagingUnits,但对于其他用途,它需要如此。

标签: typescriptenums

解决方案


TypeScript 不会将对未知属性的访问视为一种进行控制流缩小以断言该属性存在的方式。如果编译器知道(或者在可选属性的情况下可能存在)该键的属性,您只能访问对象类型的属性。因此,一旦您执行PackagingUnits[key]where keyis an absolute string,编译器就会抱怨PackagingUnits没有string索引。

解决此问题的最简单方法是使用类型断言并继续:

function getPackagingUnitName(key: string): PackagingUnits | null {      
  if ((PackagingUnits as any)[key]) { // assert your way out
    return PackagingUnits[key as keyof typeof PackagingUnits]; // assert your way out
  }
  return null;
}

这可行,但编译器不保证类型安全。您可能不在乎,因为问题在于其他开发人员不会搞砸的功能的实现。如果是这样,那就太好了。


如果您希望编译器保证您所做的事情是类型安全的,您需要通过仔细的步骤引导它进行分析。首先,您希望编译器不将PackagingUnits对象视为具有三个已知键且其值为PackagingUnits类型的对象,而是将其视为在每个字符串键处具有值的对象,其值为PackagingUnits | undefined. (这是尽可能接近可选索引签名,因为 TypeScript 并不总是将缺失的属性与undefined-values 属性区分开来)。所以你会喜欢这样:

const PackagingUnitsIndexSignature: {[k: string]: PackagingUnits | undefined} =
  PackagingUnits; // error!

这应该是安全的(类型typeof PackagingUnits是 的严格子类型) ,typeof PackagingUnitsIndexSignature但编译器没有意识到这一点。的enum性质PackagingUnits似乎混淆了它。所以我们可以进行两步扩展……首先是一个普通的对象类型,其键和值与枚举的匹配:

const PackagingUnitsPlainObject: Record<keyof typeof PackagingUnits, PackagingUnits> =
  PackagingUnits; // okay!
const PackagingUnitsIndexSignature: { [k: string]: PackagingUnits | undefined } =
  PackagingUnitsPlainObject; // okay!

现在我们终于有了可以string无错误索引的内容:

function getPackagingUnitName(key: string): PackagingUnits | null {
  if (PackagingUnitsIndexSignature[key]) { // okay!
    return PackagingUnitsIndexSignature[key];  // error!!!!
  } else {
    return null;
  }       
}

呃,怎么了?既然我们刚刚检查了属性的存在,不应该控制流量缩小工作key吗?不幸的是,括号类型的索引访问并不总是像我们想要的那样缩小值。与其强迫它工作,不如把它当作PackagingUnitsIndexSignature[key]返回的给定PackagingUnits | undefined。如果是这样,我们应该能够使用逻辑“或”(||)运算符替换undefinednull

function getPackagingUnitName(key: string): PackagingUnits | null {
  return PackagingUnitsIndexSignature[key] || null;  // okay
}

编译器很高兴。总而言之,这是编写相同逻辑的一种迂回方式,以便编译器确认它是类型安全的:

const PackagingUnitsPlainObject: Record<keyof typeof PackagingUnits, PackagingUnits> =
  PackagingUnits; // okay!
const PackagingUnitsIndexSignature: { [k: string]: PackagingUnits | undefined } =
  PackagingUnitsPlainObject; // okay!
function getPackagingUnitName(key: string): PackagingUnits | null {
  return PackagingUnitsIndexSignature[key] || null;  // okay!
}

好的,希望有帮助。祝你好运!


推荐阅读