Присвоение перечисления типу на основе указанного перечисления

#javascript #typescript

Вопрос:

Вот мой упрощенный код:

 enum DetailsDataTypes {
  MACHINE = 'MACHINE',
  USER = 'USER',
  ABSTRACT = 'ABSTRACT',
}

enum PossibleAdditionModes {
  MACHINE = DetailsDataTypes.MACHINE,
  USER = DetailsDataTypes.USER,
  ABSTRACT = DetailsDataTypes.ABSTRACT,
}

enum PossibleEditModes {
  NULL = 'null',
  EDIT = 'edit'
}

enum OptionsEnums {
  MACHINE = PossibleAdditionModes.MACHINE,
  USER = PossibleAdditionModes.USER,
  ABSTRACT = PossibleAdditionModes.ABSTRACT,
  EDIT = PossibleEditModes.EDIT,
  NULL = PossibleEditModes.NULL,
}

type OptionsType = PossibleEditModes | PossibleAdditionModes;
 

Также доступно здесь

Таким образом, проблема в том , что в ряде мест я пытаюсь назначить перечисление от OptionsEnum до OptionsType , но я получаю ошибку, говоря это Argument of type 'StateType.ABSTRACT' is not assignable to parameter of type 'Types'. .

Я получаю аналогичную ошибку на игровой площадке здесь:

 const tmp = OptionsEnums.ABSTRACT;

tmp as OptionsType;
 

Но не совсем то же самое.

Я что-то упускаю? Или я делаю что-то совершенно наоборот?

Комментарии:

1. В чем цель OptionsEnums ? Когда вы хотите этого, а не соответствующего PossibleEditModes или PossibleAdditionModes ( как это )?

2. Это часть более масштабного проекта React. OptionsEnums является типом для редуктора redux. У меня есть кусочек, который может быть "machine" , "edit" , "user" , "abstract" , или null .

3. enum на самом деле это не совсем подходящий инструмент для работы, когда вы так смешиваете и сопоставляете (на самом деле я бы сказал, что enum это почти никогда не подходит для работы). Вместо этого вы можете использовать const объекты, подобные этому , и достигать тех же эффектов без того, чтобы компилятор сомневался в вас. Если это соответствует вашим потребностям, я был бы рад написать ответ; если нет, пожалуйста, объясните (и желательно продемонстрируйте), почему вы считаете, что вашему варианту использования нужны эти enum типы, и, возможно, есть способ обойти это

4. Это какой-то запутанный код, но, пожалуйста, напишите ответ, и я был бы признателен, если бы вы могли подробнее 😉

Ответ №1:

enum TypeScript s немного странны в том, что они действительно не согласуются с большинством целей дизайна типографского текста; в частности, они не являются синтаксисом ECMAScript с эффектами времени выполнения. Они являются чем-то вроде пережитка со времен, когда такие цели языкового дизайна еще не были объединены.


Перечисления-одно из немногих мест, где типы сравниваются номинально, а не структурно; предполагается, что сравнение двух разных enum типов является ошибкой, даже если они имеют одинаковое значение во время выполнения:

 enum Foo {
  A = 0
}
enum Bar {
  A = 0
}
const foo: Foo = Bar.A;
// error! Type 'Bar' is not assignable to type 'Foo'.
 

Таким образом, если вы намеренно смешиваете и сопоставляете разные enum типы, вы, скорее всего, столкнетесь с предупреждениями компилятора.


Кроме того, некоторые из предполагаемых вариантов использования для enum s имеют некоторые, возможно, неожиданные или неприятные эффекты. Например, числовые перечисления могут использоваться для битовых полей; любое number числовое перечисление может быть присвоено любому, даже если такое число не является одним из значений перечисления; см. microsoft/TypeScript#32690).

В вашем случае вы, похоже, столкнулись с проблемой, упомянутой в microsoft/TypeScript#27976; ваши PossibleAdditionModes свойства и OptionsEnums свойства вычислены из других перечислений. Это (молчаливо) не позволяет им быть «буквальным перечислением», в котором тип перечисления считается объединением буквальных типов. Вместо этого он расширяется до простого числового перечисления, в котором компилятор больше не «видит» отдельные литеральные типы, составляющие перечисление. Такое поведение, по-видимому, является преднамеренным, но не тем, чего вы хотите.

Таким образом, любой член, скажем, OptionsEnums , будет рассматриваться только как относящийся к типу OptionsEnum :

 const hmm = OptionsEnums.ABSTRACT; // const hmm: OptionsEnums
 

И поэтому задание OptionsType выполнить не удается:

 const tmp: OptionsType = hmm; // error!
 

Здесь вы хотите, чтобы компилятор отслеживал литеральный тип OptionsEnums.ABSTRACT и позволял вам использовать его в месте, которое принимает an OptionsType . Но enum это не облегчает вам задачу.


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

Один из подходов заключается в замене enum s обычными объектами. Во время выполнения ваши enum s-это просто объект с ключами и значениями. И во время компиляции вы используете имя типа перечисления для обозначения объединения его значений, и вы хотите, чтобы компилятор отслеживал литеральные типы этих значений. Поэтому, если вы создадите простой объект и используете const утверждение, чтобы попросить компилятор отслеживать литеральные типы его значений и создать псевдоним типа для объединения этих значений, вы можете получить enum подобное поведение без всех особенностей enum . Например:

 enum Foo {
  A = 0,
  B = 1
}
 

становится

 const Foo = {
  A: 0,
  B: 1
} as const;

type Foo = (typeof Foo)[keyof typeof Foo];
 

Тип Foo значения является

 /* const Foo: {
    readonly A: 0;
    readonly B: 1;
} */
 

и тип, названный Foo так

 // type Foo = 0 | 1
 

И вдруг неисправный код из прошлого будет работать:

 const foo: Foo = Bar.A; // okay
 

Так что давайте заменим вашу enum букву s на эту:

 const DetailsDataTypes = {
  MACHINE: 'MACHINE',
  USER: 'USER',
  ABSTRACT: 'ABSTRACT',
} as const
type DetailsDataTypes = (typeof DetailsDataTypes)[keyof typeof DetailsDataTypes];

const PossibleAdditionModes = {
  MACHINE: DetailsDataTypes.MACHINE,
  USER: DetailsDataTypes.USER,
  ABSTRACT: DetailsDataTypes.ABSTRACT,
} as const
type PossibleAdditionModes = (typeof PossibleAdditionModes)[keyof typeof PossibleAdditionModes]

const PossibleEditModes = {
  NULL: 'null',
  EDIT: 'edit'
} as const;
type PossibleEditModes = (typeof PossibleEditModes)[keyof typeof PossibleEditModes]

const OptionsEnums = {
  MACHINE: PossibleAdditionModes.MACHINE,
  USER: PossibleAdditionModes.USER,
  ABSTRACT: PossibleAdditionModes.ABSTRACT,
  EDIT: PossibleEditModes.EDIT,
  NULL: PossibleEditModes.NULL,
} as const;
type OptionsEnums = (typeof OptionsEnums)[keyof typeof OptionsEnums]
 

Если вы проверите некоторые типы, вы увидите литералы:

 // type PossibleEditModes = "null" | "edit"
// type OptionsEnums = "null" | "edit" | "MACHINE" | "USER" | "ABSTRACT"
 

И поэтому ваше задание просто сработает:

 type OptionsType = PossibleEditModes | PossibleAdditionModes;
const hmm = OptionsEnums.ABSTRACT; // "ABSTRACT"
const tmp: OptionsType = hmm; // okay
 

Компилятор видит , что hmm это тип "ABSTRACT" , которому разрешено присваиваться OptionsType .

Ссылка на игровую площадку для кода

Комментарии:

1. В этом есть смысл. Так что мне также нужно было привести его в нужное перечисление, верно?

2. Я не знаю, что вы подразумеваете под «приведением его к правильному перечислению»; Я не делал никакого приведения в своем ответе (если вы не считаете const утверждение, которого я бы не сделал), поэтому я не уверен, что вы имеете в виду.