Нет, потребителю нужно будет использовать typeof MyEnum
для ссылки на объект, ключи которого A
, B
и C
.
ДЛИННОЕ ОБЪЯСНЕНИЕ ВПЕРЕД, НЕКОТОРЫЕ ИЗ КОТОРЫХ ВЫ, ВЕРОЯТНО, УЖЕ ЗНАЕТЕ
Как вы, вероятно, знаете, TypeScript добавляет статическую систему типов в JavaScript, и эта система типов стирается при переносе кода. Синтаксис TypeScript таков, что некоторые выражения и операторы ссылаются на значения , которые существуют во время выполнения, тогда как другие выражения и операторы ссылаются на типы , которые существуют только во время проектирования / компиляции. Значения имеют типов, но сами они не являются типами. Важно отметить, что в коде есть места, где компилятор будет ожидать значение и интерпретировать найденное выражение как значение, если это возможно, и другие места, где компилятор будет ожидать тип и интерпретировать найденное выражение как тип, если это возможно.
Компилятор не заботится и не смущается, если выражение можно интерпретировать как значение и тип. Он, например, совершенно доволен двумя разновидностями null
в следующем коде:
let maybeString: string | null = null;
Первый экземпляр null
является типом, а второй - значением. У него также нет проблем с
let Foo = {a: 0};
type Foo = {b: string};
, где первый Foo
является именованным значением, а второй Foo
является именованным типом. Обратите внимание, что тип значения Foo
равен {a: number}
, а тип Foo
равен {b: string}
. Они не то же самое.
Даже оператор typeof
ведет двойную жизнь. Выражение typeof x
всегда ожидает, что x
будет значением , но сам typeof x
может быть значением или типом в зависимости от контекста:
let bar = {a: 0};
let TypeofBar = typeof bar; // the value "object"
type TypeofBar = typeof bar; // the type {a: number}
Строка let TypeofBar = typeof bar;
перейдет в JavaScript и будет использовать оператор typeof JavaScript во время выполнения и создаст строку. Но type TypeofBar = typeof bar
; удаляется, и он использует оператор запроса типа TypeScript для проверки статического типа, который TypeScript присвоил значению с именем bar
.
Теперь большинство языковых конструкций в TypeScript, которые вводят имена, создают либо именованное значение, либо именованный тип. Вот некоторые введения именованных значений:
const value1 = 1;
let value2 = 2;
var value3 = 3;
function value4() {}
А вот некоторые введения именованных типов:
interface Type1 {}
type Type2 = string;
Но есть несколько объявлений, которые создают и именованное значение и именованный тип, и, как и Foo
выше, тип именованного значения не названный тип . Большие из них class
и enum
:
class Class { public prop = 0; }
enum Enum { A, B }
Здесь тип Class
- это тип экземпляра из Class
, тогда как значение Class
- это конструктор объект. И typeof Class
не Class
:
const instance = new Class(); // value instance has type (Class)
// type (Class) is essentially the same as {prop: number};
const ctor = Class; // value ctor has type (typeof Class)
// type (typeof Class) is essentially the same as new() => Class;
И тип Enum
является типом элемента перечисления; объединение типов каждого элемента. В то время как значение Enum
является объектом , чьи ключи A
и B
, а чьи свойства являются элементами перечисления. И typeof Enum
не Enum
:
const element = Math.random() < 0.5 ? Enum.A : Enum.B;
// value element has type (Enum)
// type (Enum) is essentially the same as Enum.A | Enum.B
// which is a subtype of (0 | 1)
const enumObject = Enum;
// value enumObject has type (typeof Enum)
// type (typeof Enum) is essentially the same as {A: Enum.A; B: Enum.B}
// which is a subtype of {A:0, B:1}
Теперь вернемся к вашему вопросу. Вы хотите изобрести оператор типа, который работает так:
type KeysOfEnum = EnumKeysAsStrings<Enum>; // "A" | "B"
, куда вы помещаете тип Enum
и получаете ключи от объекта Enum
. Но, как вы видите выше, тип Enum
отличается от объекта Enum
. И, к сожалению, тип ничего не знает о значении. Это как сказать:
type KeysOfEnum = EnumKeysAsString<0 | 1>; // "A" | "B"
Очевидно, что если вы напишите это так, вы увидите, что вы ничего не могли бы сделать с типом 0 | 1
, который бы выдал тип "A" | "B"
. Чтобы это работало, вам нужно передать тип, который знает о отображении. И этот тип typeof Enum
...
type KeysOfEnum = EnumKeysAsStrings<typeof Enum>;
что похоже на
type KeysOfEnum = EnumKeysAsString<{A:0, B:1}>; // "A" | "B"
, что возможно возможно ... если type EnumKeysAsString<T> = keyof T
.
Итак, вы застряли, заставляя потребителя указать typeof Enum
.Есть ли обходные пути?Что ж, возможно, вы могли бы использовать что-то, что делает это значение, например, функцию?
function enumKeysAsString<TEnum>(theEnum: TEnum): keyof TEnum {
// eliminate numeric keys
const keys = Object.keys(theEnum).filter(x =>
(+x)+"" !== x) as (keyof TEnum)[];
// return some random key
return keys[Math.floor(Math.random()*keys.length)];
}
Тогда вы можете вызвать
const someKey = enumKeysAsString(Enum);
и тип someKey
будет "A" | "B"
.Да, но затем, чтобы использовать его как type , вам нужно будет запросить его:
type KeysOfEnum = typeof someKey;
, что заставляет вас снова использовать typeof
и еще более многословно, чем ваше решение, особеннотак как вы не можете сделать это:
type KeysOfEnum = typeof enumKeysAsString(Enum); // error
Блех.Извините.
ЗАПИСАТЬ:
- ЭТО НЕ ВОЗМОЖНО;
- ВИДЫ И ЦЕННОСТИ BLAH BLAH;
- ЕЩЕ НЕ ВОЗМОЖНО;
- К сожалению.
Надеюсь, что это имеет какой-то смысл.Удачи.