Cum să obții toate steagurile Enum dintr-un număr în TypeScript?


Dat:
Enum: {1, 4, 16}
Flaguri: 20

Când: transmit Steaguri către o funcție

Apoi: Obțin un array de steaguri pentru Enum-ul dat: [4, 16]

Notă: Am încercat să convertesc Enum în array manual și să tratez valorile ca numere. Dar mă simt copleșit când ajunge la TS și vreau să fac acea funcție dinamică în cazul în care Enum se schimbă. Chiar nu vreau să codific asta direct. Vă rog să mă ajutați!

enum Enum {
    one = 1,
    four = 4,
    sixteen = 16,
}

const Flags: number = 20;

function getEnumValues(flags: number): Enum[] {
    const enumFlags = [1, 4, 16];
    
    const enums: Enum[] = [];

    enumFlags.forEach(ef => {
        if ((ef & flags) != 0) {
            enums.push(ef);
        }
    })

    return enums;
}

console.log(getEnumValues(Flags));

Soluția găsită aici: Typescript semnalează că obținerea valorilor unui enum nu este bună deoarece Enum-ul meu nu este o secvență, 2 și 8 lipsesc.

Răspuns #1

Am întâlnit o provocare similară în trecut. Și, din întâmplare, am scris un articol despre asta, pentru că a fost o provocare cu adevărat interesantă.

Primul lucru de care ai nevoie este un ajutor care să extragă steagurile Enum ca un array, tratând enum din TS ca pe un obiect obișnuit. După aceea, abordarea ta este gata de utilizare, parcurgând steagurile și verificând dacă este prezent în numărul tău.

Fragment copiat din sandbox-ul de cod al linkului de mai sus.

const isPowerOfTwo = (x: number): boolean => {
  return x != 0 && (x & (x - 1)) == 0;
};

export function getEnumFlags<
  O extends object,
  K extends O[keyof O] = O[keyof O]
>(obj: O): K[] {
  const isFlag = (arg: string | number | K): arg is K => {
    const nArg = Number(arg);
    const isNumber = !Number.isNaN(nArg);
    return isNumber && isPowerOfTwo(nArg);
  };

  const enumFlags: K[] = [];

  Object.keys(obj).forEach(key => {
    const nKey = Number(key);
    if (isFlag(nKey)) {
      enumFlags.push(nKey);
    }
  });

  return enumFlags;
}

Edit 2024.06.12: Răspunsul meu se ocupă de cazurile-limită atunci când enumurile au valori intermediare folosite pentru comoditate. De exemplu: enum MyEnum { first=1, second=2, both=3 } - both nu este o flag, ci o comoditate în loc să scrii first | second.

Răspunsul #2

Dacă te îngrijorează faptul că repeți valorile Enum în array-ul enumFlags din funcție, le poți obține dinamic:

const enumFlags = Object.values(Enum)
    .filter((value): value is number => typeof value === "number");

Trebuie să faci ca funcția de callback filter să fie o funcție de tip guard (folosind un predicat de tip, value is number) pentru că altfel TypeScript crede că array-ul este de tip (string | Enum)[]. Dar știm că nu este, pentru că facem typeof === "number".

Link către terenul de joacă

Deoarece construirea acelui array nu este gratuită, poate merita să faci asta o singură dată, imediat după definiția Enum, și să-l refolosești. Totuși, deși nu este gratuit, este foarte ieftin (și păstrarea lui nu este gratuită nici: ocupă [puțin] memorie).