#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
утверждение, которого я бы не сделал), поэтому я не уверен, что вы имеете в виду.