代码之家  ›  专栏  ›  技术社区  ›  John

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

  •  0
  • John  · 技术社区  · 5 年前

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

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

    我有以下枚举:

    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.
    
     }
    }
    

    我想知道我该如何解决这个问题。我如何确保 attackType 当我只知道字符串文字(例如“close”)时是否正确分配?

    我可以用“奇怪”的方式( AttackType[<string>AttackType['close']] )指定正确的枚举值,使其与 攻击类型.melee 但我不确定这是否是一个好的方法。

    1 回复  |  直到 5 年前
        1
  •  1
  •   jcalz    5 年前

    我对试图欺骗编译器生成 reverse mappings 对于字符串枚举 <any> 类型断言。反向字符串映射是故意省略的,因为 @RyanCavanaugh 在A中说 relevant 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] . 然后返回类型 回复枚举() 是一个 mapped type 其键来自枚举 价值观 ,其值来自对应项的键 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"
    

    你可以看到这个值 TwoWayAttackType 与具有相同类型的 AttackType 枚举常量,具有额外属性 {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型 只是 攻击类型 (然后也许是我的电话 攻击类型 会有点像 OneWayAttackType BaseAttackType )

    好吧,希望有帮助,祝你好运!