Vyhledávání na webu

Jak porovnávat Enumy v TypeScriptu


V TypeScriptu chci porovnat dvě proměnné obsahující hodnoty výčtu. Zde je můj minimální příklad kódu:

enum E {
  A,
  B
}

let e1: E = E.A
let e2: E = E.B

if (e1 === e2) {
  console.log("equal")
}

Při kompilaci s tsc (verze 2.0.3) dostávám následující chybu:

TS2365: Operátor '===' nelze použít na typy 'E.A' a 'E.B'.

Totéž platí pro ==, !== a !=. Zkoušel jsem přidat klíčové slovo const, ale zdá se, že to nemá žádný efekt. Specifikace TypeScriptu říká následující:

4.19.3 Operátory <, >, <=, >=, ==, !=, === a !==

Tyto operátory vyžadují, aby jeden nebo oba typy operandů byly přiřaditelné k tomu druhému. Výsledek je vždy typu primitivní boolean.

Což (myslím) vysvětluje chybu. Ale jak se tomu mohu vyhnout?

Poznámka
Používám editor Atom s atom-typescript a ve svém editoru nezískávám žádné chyby/varování. Ale když spustím tsc ve stejném adresáři, dostanu výše uvedenou chybu. Myslel jsem, že by měly používat stejný soubor tsconfig.json, ale očividně to tak není.

Odpověď č. 1

No, myslím, že jsem našel něco, co funguje:

if (e1.valueOf() === e2.valueOf()) {
  console.log("equal")
}

Ale jsem trochu překvapený, že se o tom v dokumentaci nikde nepíše.

Odpověď č. 2

Existuje další způsob: pokud nechcete, aby generovaný kód JavaScript byl jakkoli ovlivněn, můžete použít přetypování:

let e1: E = E.A
let e2: E = E.B


if (e1 as E === e2 as E) {
  console.log("equal")
}

Obecně je to způsobeno odvozováním typu založeným na toku řízení. S aktuální implementací TypeScriptu je to vypnuto vždy, když je zapojeno volání funkce, takže to můžete udělat i takto:

let id = a => a

let e1: E = id(E.A)
let e2: E = id(E.B)

if (e1 === e2) {
  console.log('equal');
}

Divné je, že stále nedochází k chybě, pokud je funkce id deklarována tak, aby přesně vracela stejný typ jako její argument:

function id<T>(t: T): T { return t; }

Odpověď č. 3

Hodnoty pro Enum bych definoval takto a porovnával s ===

const enum AnimalInfo {
Tiger = "Tiger",
Lion = "Lion"
}

let tigerStr = "Tiger";

if (tigerStr === AnimalInfo.Tiger) {
  console.log('true');
} else {
  console.log('false');
}

Odpověď č. 4

Kdyby bylo možné porovnat dvě výčtové typy s tímto

 if (product.ProductType && 
       (product.ProductType.toString() == ProductTypes[ProductTypes.Merchandises])) {
      // yes this item is of merchandises
  } 

s ProductTypes being this export enum ProductTypes{Merchandises,Goods,...}

Odpověď č. 5

Originál AUG/18

V mém případě žádné z výše uvedených řešení nefungovalo, důvodem bylo, že jsem přetypovával hodnotu výčtu na objekt výčtu.

Poté jsem se snažil zjistit, zda je enum ekvivalentní jinému objektu enum... takže jsem vytvořil následující generické funkce:

  public static enumEquals<T>(e: any, e1: T, e2: T): boolean {
    const v1 = this.enumValue(e, e1);
    return v1 === this.enumValue(e, e2, typeof v1);
  }

  private static enumValue<T>(enumType: any, value: T, validType?: string) {
    let v = enumType[value];
    if (!validType) {
      return v;
    }
    if (typeof v !== validType) {
      v = enumType[v];
    }
    if (typeof v !== validType) {
      v = enumType[v];
    }
    return v || null;
  }

Toto je příklad mého testovacího případu:

enum SomeEnum {
  VALUE1, VALUE2, VALUE3, VALUE_DEF
}

const enumRefKey = localStorage.getItem('someKey');
const parsedEnum = SomeEnum[enumRefKey] || SomeEnum.VALUE_DEF;
console.log(parsedEnum);
if (parsedEnum === SomeEnum.VALUE_DEF) {
  // do stuff
}

Ten kód nefungoval, po tom, co jsem vyzkoušel řešení uvedená zde u této otázky, jsem zjistil, že když je enumRefKey platný, console.log(parsedEnum) vypisoval čísla a text VALUE_DEF, když není. Stejný výsledek se stal při použití všech ostatních řešení:

  • parsedEnum jako SomeEnum
  • parsedEnum.valueOf()
  • SomeEnum[parsedEnum]

Řešení využívající generické metody vypadá takto:

enum SomeEnum {
  VALUE1, VALUE2, VALUE3, VALUE_DEF
}

const enumRefKey = localStorage.getItem('someKey');
const parsedEnum = SomeEnum[enumRefKey] || SomeEnum.VALUE_DEF;
console.log(parsedEnum);
if (this.enumEquals(SomeEnum, parsedEnum, SomeEnum.VALUE_DEF) {
  // do stuff
}

Aktualizace SEP/21

Nejlepším způsobem, jak se vyhnout všem problémům souvisejícím s enums při porovnávání v TypeScript, je je deklarovat podle následujícího příkladu.

Místo toho:

enum SomeEnum {
  VALUE1, VALUE2, VALUE3
}

Udělěj toto:

enum SomeEnum {
  VALUE1 = 'VALUE1', VALUE2 = 'VALUE2', VALUE3 = 'VALUE3'
}

Tímto způsobem od nynějška nebudete muset přetypovávat ani konvertovat hodnoty výčtu na objekty výčtu, a pokud to budete potřebovat, vždy to bude fungovat. S tímto řešením jsou všechny následující příklady platné a vrátí true:

console.log(SomeEnum['VALUE1'] === 'VALUE1');             // prints 'true'
console.log(SomeEnum['VALUE1'] === SomeEnum.VALUE1);      // prints 'true'
console.log(SomeEnum['VALUE1'] === 'VALUE1' as SomeEnum); // prints 'true'
console.log(SomeEnum['VALUE1'] === 'VALUE1');             // prints 'true'
console.log(SomeEnum['VALUE1'] === (<SomeEnum>'VALUE1')); // prints 'true'
console.log(SomeEnum.VALUE1 === 'VALUE1' as SomeEnum);    // prints 'true'
console.log(SomeEnum.VALUE1 === (<SomeEnum>'VALUE1'));    // prints 'true'
console.log(SomeEnum.VALUE1 === 'VALUE1');                // prints 'true'

Pachatel

Důvodem všech těchto problémů je, že když se TypeScript převede na JavaScript, jsou výčty (enums) zpracovávány jako objekty takto

// this enum at TS
enum SomeEnum {
  VALUE1, VALUE2, VALUE3
}
// is parsed to JS like this:
{
  VALUE1: 1, VALUE2: 2, VALUE3: 3, 1: 'VALUE1', 2: 'VALUE2', 3: 'VALUE3'
} 

Jak můžete vidět, jakmile je výčet zpracován do JS, důvod všech problémů s porovnáváním je zřejmý, protože můžeme omylem porovnávat string s number, což může vést k falešně pozitivním výsledkům. Následuje druhý výčet zpracovaný do JS, který funguje mnohem lépe:

// this enum at TS
enum SomeEnum {
  VALUE1 = 'VALUE1', VALUE2 = 'VALUE2', VALUE3 = 'VALUE3'
}
// is parsed to JS like this:
{
  'VALUE1': 'VALUE1', 'VALUE2': 'VALUE2', 'VALUE3': 'VALUE3'
} 

Související články