Общий тип для получения ключей перечисления в виде строки объединения в машинописи? - PullRequest
0 голосов
/ 16 мая 2018

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

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 AnyEnumAsUntypedKeys<TEnum> = keyof TEnum;
type MyEnumKeysAsStrings = AnyEnumAsUntypedKeys<typeof MyEnum>; // works, but not kind to consumer.  Ick.

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

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

1 Ответ

0 голосов
/ 17 мая 2018

Нет, потребителю нужно будет использовать 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;
  • ЕЩЕ НЕ ВОЗМОЖНО;
  • К сожалению.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...