如何处理打字稿中具有不同字符串文字的字符串枚举的反向映射?

时间:2019-01-04 12:38:20

标签: typescript enums

也有一些类似的问题,例如:

我的示例不同于其他链接的问题,因为我使用的字符串文字与枚举成员名称不同。

我有以下枚举:

export enum AttackType {
  MELEE = <any>'close', // I had to use <any> to make it work at all
  RANGED = <any>'far'
}

我想根据分配的字符串文字获取正确的枚举。 我以为AttackType['close']AttackType.MELEE相同,但事实并非如此。前者打印MELEE,而后者打印close,使以下语句为假

AttackType['close']===AttackType.MELEE

所以如果我有以下课程:

export class Hero{
 attackType: AttackType;
 constructor(){
  this.attackType = AttackType['close']; // no errors, and prints MELEE,
  this.attackType = AttackType.MELEE; // no errors, and prints close,
  this.attackType = AttackType[AttackType['close']]; // compile error.
  this.attackType = AttackType[<string>AttackType['close']]; // no errors, and prints close.

 }
}

我想知道如何解决这个问题。当我只知道字符串文字(例如'close')时,如何确保正确分配attackType

我可以使用“奇怪的”方式(AttackType[<string>AttackType['close']])来分配正确的枚举值,使其与AttackType.MELEE相同,但是我不确定这是否是一个好方法。

1 个答案:

答案 0 :(得分:2)

对于使用该<any>类型断言试图愚弄编译器为字符串枚举生成reverse mappings的想法,我感到不满意。 @RyanCavanaughrelevant GitHub issue中说过,故意省略了反向字符串映射:

  

如果我们自动提供了反向映射(顺便说一句,它不一定是明确的!),那么除非在运行时创建了一个完全独立的对象,否则您将无法将键与值区分开。如果需要的话,编写一个构造反向映射的函数很简单;我们的目标是发出尽可能少的代码,因此仅发出需要的数据(以减小代码大小)并让您根据需要构造反向映射是有意义的。

我认为,如果继续使用该技巧,您将发现自己需要像所做的那样断言,并且如果将来的TypeScript版本完全破坏它,您也不会感到惊讶。

但是,如果TypeScript的开发负责人说“编写构成反向映射的函数很简单”,那么我们可以尝试一下,看看它如何进行。那么,您将如何处理?在运行时,您实际上只需要遍历枚举对象条目并使用切换的键和值生成一个新对象。而且,如果您想在同一个对象中同时使用正向和反向映射,则可以将常规枚举中的属性merge转换为反向枚举:

type Entries<T extends object> = { [K in keyof T]: [K, T[K]] }[keyof T]

function reverseEnum<E extends Record<keyof E, string | number>>(
  e: E
): { [K in E[keyof E]]: Extract<Entries<E>, [any, K]>[0] };
function reverseEnum(
  e: Record<string | number, string | number>
): Record<string | number, string | number> {
  const ret: Record<string | number, string | number> = {};
  Object.keys(e).forEach(k => ret[e[k]] = k);
  return ret;
}

function twoWayEnum<E extends Record<keyof E, string | number>>(e: E) {
  return Object.assign(reverseEnum(e), e);
}

reverseEnum()的签名涉及一些类型的杂耍。类型函数Entries<T>将对象类型T转换为键值对的并集,例如Entries<{a: string, b: number}>的值为["a",string] | ["b",number]。然后,reverseEnum()的返回类型是mapped type,其键来自枚举 values ,其值来自于由extracting进行相应输入的键。让我们看看它是否有效:

enum AttackType {
  MELEE = 'close',
  RANGED = 'far'
}

const TwoWayAttackType = twoWayEnum(AttackType);
// const TwoWayAttackType = {
//   close: "MELEE";
//   far: "RANGED";
// } & typeof AttackType

// might as well make a type called TwoWayAttackType also, 
// corresponding to the property values of the TwoWayAttackType object
type TwoWayAttackType = AttackType | keyof typeof AttackType

console.log(TwoWayAttackType.close); // "MELEE"
console.log(TwoWayAttackType[TwoWayAttackType.far]) // "far"

您会看到值TwoWayAttackTypeAttackType枚举常量具有相同的类型,并具有附加的属性{close: "MELEE", far: "RANGED"}。一种麻烦是TypeScript不会自动生成与TwoWayAttackType常量的属性类型相对应的名为TwoWayAttackType的类型,因此,如上文所述,如果您想要一个常量,我们必须手动创建它。

现在,您应该能够按需进行课堂学习而不会出现类型错误:

class Hero {
  attackType: TwoWayAttackType;
  constructor() {
    this.attackType = TwoWayAttackType['close']; 
    this.attackType = TwoWayAttackType.MELEE; 
    this.attackType = TwoWayAttackType[TwoWayAttackType['close']]; 
  }
}

请注意,如果此方法对您有用,那么您始终可以重命名上面的值/类型,这样我叫TwoWayAttackType就是AttackType(然后也许就是我叫{ {1}}类似于AttackTypeOneWayAttackType)。

好的,希望能有所帮助;祝你好运!