Enum как параметр в typescript

Невозможно ли установить тип параметра в Enum? Вот так:

private getRandomElementOfEnum(e : enum):string{
    var length:number = Object.keys(e).length;
    return e[Math.floor((Math.random() * length)+1)];
}

Если я это сделаю, Intellij отметит этот код как неизвестный. И предложить переименовать переменную, имеет ли это смысл?

private getRandomElementOfEnum(e : any):string{
    var length:number = Object.keys(e).length;
    return e[Math.floor((Math.random() * length)+1)];
}

Этот код работает нормально. но не столь изящным и понятным для кода.

Есть ли возможность или небольшое обходное решение для определения перечисления в качестве параметра?

EDIT:

Изучив эти ответы, могу ли я сделать это также с ah набором определенных enum, sth, подобным enum1 | enum2 | enum3?

Ответ 1

Невозможно гарантировать, что параметр является перечислением, потому что перечисления в TS не наследуются от общего предка или интерфейса.

TypeScript обеспечивает статический анализ. В вашем коде используется динамическое программирование с Object.keys и e[dynamicKey]. Для динамических кодов удобно использовать тип any.

Ваш код неисправен: length() не существует, e[Math.floor((Math.random() * length)+1)] возвращает строку или целое число, а значения перечисления могут быть установлены вручную...

Вот предложение:

function getRandomElementOfEnum<E>(e: any): E {
    var keys = Object.keys(e),
        index = Math.floor(Math.random() * keys.length),
        k = keys[index];
    if (typeof e[k] === 'number')
        return <any>e[k];
    return <any>parseInt(k, 10);
}

function display(a: Color) {
    console.log(a);
}

enum Color { Blue, Green };
display(getRandomElementOfEnum<Color>(Color));

В идеале, тип параметра any должен быть заменен на typeof E, но компилятор (TS 1.5) не может понять этот синтаксис.

Ответ 2

Вы можете сделать лучше, чем any:

enum E1 {
    A, B, C
}
enum E2 {
    X, Y, Z
}

function getRandomElementOfEnum(e: { [s: number]: string }): number {
    /* insert working implementation here */
    return undefined;
}

// OK
var x: E1 = getRandomElementOfEnum(E1);
// Error
var y: E2 = getRandomElementOfEnum(window);
// Error
var z: string = getRandomElementOfEnum(E2);

Ответ 3

Я согласен с @Tarh. Перечисления в TypeScript - это просто объекты Javascript без общего интерфейса или прототипа (и если они const enum, то они даже не являются объектами), поэтому вы не можете ограничивать типы "любым перечислением".

Самое близкое, что я мог получить, это что-то вроде следующего:

enum E1 {
    A, B, C
}
enum E2 {
    X, Y, Z
}

// make up your own interface to match TypeScript enums
// as closely as possible (not perfect, though)
interface Enum {
    [id: number]: string
}

function getRandomElementOfEnum(e: Enum): string {
   let length = Object.keys(e).length / 2;
   return e[Math.floor((Math.random() * length))];
}

Это работает для всех перечислений (без пользовательских инициализаторов), но оно также будет принимать другие массивы в качестве входных данных (а затем завершаться ошибкой, поскольку тело метода опирается на очень специфическую структуру ключей, имеющуюся в перечислениях TypeScript).

Поэтому, если у вас нет реальной необходимости в такой "универсальной" функции, создайте безопасные для типов типы для отдельных типов перечислений (или типа объединения, например E1|E2|E3), которые вам действительно нужны.

И если у вас есть такая необходимость (и это вполне может быть проблема X-Y, которую можно решить лучше, совершенно другим способом, учитывая больший контекст), используйте any, потому что вы все равно оставили безопасную для типов территорию.

Ответ 4

Другой возможный вариант, не упомянутый выше, это использование фактических значений. Однако это возможно только тогда, когда вы знаете все варианты. Это, на мой взгляд, определенно лучше, чем любой.

    doSomething(a: string, b: 'this'|'can'|'work'): void {
     //do something
    }

Ответ 5

Суммируя предыдущие ответы с небольшим новым синтаксисом - универсальной типобезопасной функцией, которая работает как с числовыми, так и с строковыми перечислениями:

function getRandomElementOfEnum<T extends {[key: number]: string | number}>(e: T): T[keyof T] {
  const keys = Object.keys(e);

  const randomKeyIndex = Math.floor(Math.random() * keys.length);
  const randomKey = keys[randomKeyIndex];

  // Numeric enums members also get a reverse mapping from enum values to enum names.
  // So, if a key is a number, actually it a value of a numeric enum.
  // see https://www.typescriptlang.org/docs/handbook/enums.html#reverse-mappings
  const randomKeyNumber = Number(randomKey);
  return isNaN(randomKeyNumber)
    ? e[randomKey as keyof T]
    : randomKeyNumber as unknown as T[keyof T];
}

Ответ 6

Решение @selinathat отлично подходит, только если у вас есть несколько типов. но что, если у нас будет больше? например:

doSomething(a: string, b: 'this'|'can'|'work'|'test1'|'test2'|'test3'): void {
 //do something
}

это довольно уродливо, ха!? я предпочитаю использовать keyof :

interface Items {
    'this',
    'can',
    'work',
    'test1',
    'test2',
    'test3',
}

doSomething(a: string, b: keyof Items): void {
 //do something
}