typescript - Общий тип для получения ключей перечисления в виде строки объединения в машинописи?



generics enums (1)

Нет, потребителю нужно будет использовать 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 0 | 1 который будет производить тип "A" | "B" "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" "A" | "B" . Да, но затем, чтобы использовать его как тип, вам нужно запросить его:

 type KeysOfEnum = typeof someKey;

что заставляет вас снова использовать typeof и даже более многословно, чем ваше решение, тем более что вы не можете сделать это:

 type KeysOfEnum = typeof enumKeysAsString(Enum); // error

Blegh. Сожалею.

ЗАПИСАТЬ:

  • ЭТО НЕВОЗМОЖНО;
  • ВИДЫ И ЦЕННОСТИ БЛА БЛА;
  • ЕЩЕ НЕ ВОЗМОЖНО;
  • ИЗВИНЯЮСЬ.

Надеюсь, что это имеет смысл. Удачи.

Рассмотрим следующее перечисление машинописи:

enum MyEnum { A, B, C };

Если мне нужен другой тип, представляющий собой объединенные строки ключей этого перечисления, я могу сделать следующее:

type MyEnumKeysAsStrings = keyof typeof MyEnum;  // "A" | "B" | "C"

Это очень полезно.

Теперь я хочу создать универсальный тип, который будет работать с перечислениями таким образом, чтобы я мог вместо этого сказать:

type MyEnumKeysAsStrings = AnyEnumKeysAsStrings<MyEnum>;

Я думаю, правильный синтаксис для этого будет:

type AnyEnumKeysAsStrings<TEnum> = keyof typeof TEnum; // TS Error: 'TEnum' only refers to a type, but is being used as a value here.

Но это приводит к ошибке компиляции: «TEnum» относится только к типу, но используется здесь как значение ».

Это неожиданно и грустно. Я могу не полностью обойти это следующим образом, удалив typeof с правой стороны объявления универсального и добавив его к параметру type в объявлении определенного типа:

type AnyEnumAsUntypedKeys<TEnum> = keyof TEnum;
type MyEnumKeysAsStrings = AnyEnumAsUntypedKeys<typeof MyEnum>; // works, but not kind to consumer.  Ick.

Мне не нравится этот обходной путь, потому что это означает, что потребитель должен помнить, чтобы сделать эту простую указание typeof на универсальном.

Есть ли какой-то синтаксис, который позволит мне указать общий тип, как я изначально хочу, чтобы быть добрым к потребителю?





enums