如何处理打字稿中具有不同字符串文字的字符串枚举的反向映射? [英] How to handle reverse mapping of string enums with different string literals in typescript?

查看:58
本文介绍了如何处理打字稿中具有不同字符串文字的字符串枚举的反向映射?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

There have been some similar questions, like:

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

My example is different from the other linked questions because I am using a string literal which is different from the enum member name.

我有以下枚举:

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 ,使以下语句为假

I want to get the correct enum based on the assigned string literal. I thought AttackType['close'] would be the same as AttackType.MELEE, but it is not. The former prints MELEE, and the latter prints close, making the following statement false

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

所以,如果我有以下课程:

So If I have the following class:

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 ?

I am wondering how I should tackle this problem. How can I make sure that the attackType is assigned properly when I only know the string literal (e.g. 'close')?

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

I could use the "strange" way (AttackType[<string>AttackType['close']]) to assign the correct enum value, making it be the same as AttackType.MELEE, but I am not sure if this a good way of doing it.

推荐答案

我对尝试愚弄编译器以生成 @RyanCavanaugh

I'm not happy about the idea of trying to fool the compiler into generating reverse mappings for string enums by using that <any> type assertion. The reverse string mappings are omitted on purpose, as @RyanCavanaugh said in a relevant GitHub issue:

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

If we provided the reverse map automatically (which, by the way, is not necessarily non-ambiguous!), then you'd have no way to distinguish keys from values unless there was a completely separate object created at runtime. It is trivial to write a function that constructs the reverse map if you want it; our goal is to emit as little code as possible so it makes sense to just emit the data you need (to keep code size down) and let you construct the reverse map on an as-needed basis.

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

I think if you continue using that trick, you will find yourself needing to do assertions like you're doing, and you shouldn't be surprised if some future version of TypeScript breaks it entirely.

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

But if a development lead for TypeScript says "it is trivial to write a function that constructs the reverse map", then we can try that and see how it goes. So how would you go about that? At runtime you really just have to iterate through the enum object entries and produce a new object with the keys and values switched. And if you want both the forward and reverse map in the same object, you can merge the properties from the regular enum into the reversed one:

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:字符串,b:数字}> 的结果为 [" a,string] |["b",number] .那么 reverseEnum()的返回类型是

The signature for reverseEnum() involves a bit of type juggling. The type function Entries<T> turns an object type T into a union of key value pairs., e.g., Entries<{a: string, b: number}> evaluates to ["a",string] | ["b",number]. Then the return type of reverseEnum() is a mapped type whose keys come from the enum values, and whose values come from the key for corresponding entry by extracting it. Let's see if it works:

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"

您会看到 TwoWayAttackType 值与 AttackType 枚举常量具有相同的类型,并具有额外的属性 {close:"MELEE",far:"RANGED"} .一种麻烦是TypeScript不会自动生成与 TwoWayAttackType 常量的属性类型相对应的名为 TwoWayAttackType 的类型,因此,如果要使用该类型,我们必须手动将其设置为我已经完成了.

You can see that the value TwoWayAttackType has the same type as the AttackType enum constant, with extra properties {close: "MELEE", far: "RANGED"}. One wrinkle is that TypeScript does not automatically generate a type named TwoWayAttackType corresponding to the property types of the TwoWayAttackType constant, so if you want one we have to make it manually, as I've done above.

现在,您应该可以根据需要进行课堂学习了,而不会出现类型错误:

Now you should be able to make your class as desired with no type errors:

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

请注意,如果此方法对您有用,则您始终可以重命名上面的值/类型,以便我所说的 TwoWayAttackType 只是 AttackType (然后,也许我所说的 AttackType 应该是 OneWayAttackType BaseAttackType ).

Note that if this method works for you, you can always rename the values/types above so that what I'm calling TwoWayAttackType is just AttackType (and then perhaps what I'm calling AttackType would be something like OneWayAttackType or BaseAttackType).

Playground链接到代码

这篇关于如何处理打字稿中具有不同字符串文字的字符串枚举的反向映射?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆