С тех пор, как TypeScript представил типы союзов, мне интересно, есть ли причина объявлять тип перечисления. Рассмотрим следующее объявление типа перечисления:

enum X { A, B, C }
var x: X = X.A;

и аналогичное объявление типа объединения:

type X: "A" | "B" | "C"
var x: X = "A";

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

prmph

Ответов: 5

Ответы (5)

Насколько я понимаю, они не являются избыточными по той очень простой причине, что типы объединения являются чисто концепцией времени компиляции, тогда как перечисления фактически переносятся и заканчиваются в результирующем javascript (образец).

Это позволяет вам делать некоторые вещи с перечислениями, которые в противном случае невозможны с типами объединения (например, перечисление возможных значений перечисления)

Есть несколько причин, по которым вы можете захотеть использовать enum

Я вижу большие преимущества использования union в том, что они обеспечивают лаконичный способ представления значения с несколькими типами, и они очень удобочитаемы. пусть x: число | строка

РЕДАКТИРОВАТЬ: Начиная с TypeScript 2.4 Enums теперь поддерживает строки.

enum Colors {
  Red = "RED",
  Green = "GREEN",
  Blue = "BLUE",
} 

Перечисления можно концептуально рассматривать как подмножество типов объединения, предназначенных для значений int и / или string, с некоторыми дополнительными функциями, упомянутыми в других ответах, которые делают их удобными для использовать, напримерпространство имен.

Что касается безопасности типов, числовые перечисления являются менее безопасными, затем идут типы объединения и, наконец, перечисления строк:

// Числовое перечисление
enum Colors {Красный, Зеленый, Синий}
const c: Цвета = 100; // ⚠️ Ошибок нет!

// Эквивалентные типы объединения
тип Color =
    | 0 | 'Красный'
    | 1 | 'Зеленый'
    | 2 | 'Синий';

пусть цвет: Color = 'Красный'; // ✔️ Нет ошибки, потому что пространство имен свободно
цвет = 100; // ✔️ Ошибка: тип '100' нельзя присвоить типу 'Color'

введите AltColor = 'Red' | «Желтый» | 'Синий';

пусть altColor: AltColor = 'Красный';
color = altColor; // ⚠️ Нет ошибки, потому что тип `altColor` здесь сужен до` "Red" `

// Перечисление строк
enum NamedColors {
  Красный = 'Красный',
  Зеленый = 'Зеленый',
  Синий = 'Синий',
}

пусть namedColor: NamedColors = 'Красный'; // ✔️ Ошибка: тип «Красный» нельзя присвоить типу «Цвета».

enum AltNamedColors {
  Красный = 'Красный',
  Желтый = "желтый",
  Синий = 'Синий',
}
namedColor = AltNamedColors.Red; // ✔️ Ошибка: тип AltNamedColors.Red нельзя присвоить типу Colors.

Подробнее на эту тему в этой статье 2ality: Перечисления TypeScript: как они работают? Для чего их можно использовать?


Типы Union поддерживают разнородные данные и структуры, включая полиморфизм, например:

класс RGB {
    конструктор(
        только для чтения r: число,
        только для чтения g: число,
        только чтение b: число) {}

    toHSL () {
        вернуть новый HSL (0, 0, 0); // Поддельная формула
    }
}

class HSL {
    конструктор(
        только чтение h: число,
        только для чтения s: число,
        только чтение l: число) {}

    lighten () {
        вернуть новый HSL (this.h, this.s, this.l + 10);
    }
}

функция lightenColor (c: RGB | HSL) {
    return (c instanceof RGB? c.toHSL (): c) .lighten ();
}

Между перечислениями и типами объединения синглтоны могут заменять перечисления. Он более подробный, но и более объектно-ориентированный:

класс Color {
    статические только для чтения Red = new Color (1, 'Red', '# FF0000');
    статические только для чтения Green = new Color (2, 'Green', '# 00FF00');
    статические только для чтения Blue = new Color (3, 'Blue', '# 0000FF');

    статические только для чтения Все: только для чтения Цвет [] = [
        Красный цвет,
        Цвет: зеленый,
        Цвет синий,
    ];

    частный конструктор (
        только для чтения id: номер,
        метка только для чтения: строка,
        только для чтения шестнадцатеричный: строка) {}
}

const c = Color.Red;

const colorIds = Color.All.map (x => x.id);

Я обычно смотрю на F #, чтобы увидеть хорошие практики моделирования. Цитата из статьи о перечислениях F # на F # для удовольствия и выгоды, которая может быть здесь полезна:

В общем, вы должны предпочесть размеченные типы объединения перечислениям, если вам действительно не нужно иметь int (или string) значение, связанное с ними

Существуют и другие альтернативы перечислениям моделей. Некоторые из них хорошо описаны в этой другой статье о 2ality Альтернативы перечислениям в TypeScript.

В последних версиях TypeScript легко объявлять итерируемые типы объединения. Следовательно, вы должны предпочитать типы объединения перечислениям.

Как объявлять итерируемые типы объединения

const permissions = ['read', 'write', 'execute'] as const;
type Permission = typeof permissions[number]; // 'read' | 'write' | 'execute'

// you can iterate over permissions
for (const permission of permissions) {
  // do something
}

Когда фактические значения типа union не очень хорошо описывают себя, вы можете назвать их так же, как и с перечислениями.

// when you use enum
enum Permission {
  Read = 'r',
  Write = 'w',
  Execute = 'x'
}

// union type equivalent
const Permission = {
  Read: 'r',
  Write: 'w',
  Execute: 'x'
} as const;
type Permission = typeof Permission[keyof typeof Permission]; // 'r' | 'w' | 'x'

// of course it's quite easy to iterate over
for (const permission of Object.values(Permission)) {
  // do something
}

Не пропустите как утверждение const, которое играет решающую роль в этих шаблонах.

Почему нельзя использовать перечисления?

1. Неконстантные перечисления не подходят под понятие «типизированный надмножество JavaScript»

Я думаю, что эта концепция - одна из важнейших причин, почему TypeScript стал таким популярным среди других языков altJS. Неконстантные перечисления нарушают концепцию, создавая объекты JavaScript, которые живут во время выполнения, с синтаксисом, несовместимым с JavaScript.

2. Константные перечисления имеют некоторые подводные камни

Перечисления Const не могут быть перенесены с помощью Babel

В настоящее время существует два обходных пути для этой проблемы: избавиться от константных перечислений вручную или с помощью плагина babel-plugin-const-enum.

Объявление константных перечислений в окружающем контексте может быть проблематичным

Перечисления окружающих констант не допускаются, если указан флаг - isolatedModules. Член команды TypeScript говорит, что «const enum на DT действительно не имеет смысла» (DT относится к DefinentyTyped) и «Вы должны использовать тип объединения литералов (строка или число) вместо " константных перечислений в окружающем контексте.

Перечисления констант под - флаг isolatedModules ведет себя странно даже вне окружающего контекста

Я был удивлен, прочитав этот комментарий на GitHub и подтвердил, что поведение по-прежнему верно с TypeScript 3.8.2.

3. Числовые перечисления небезопасны по типу

Числовым перечислениям можно присвоить любое число.

enum ZeroOrOne {
  Zero = 0,
  One = 1
}
const zeroOrOne: ZeroOrOne = 2; // no error!!

4. Объявление перечислений строк может быть избыточным

Иногда мы видим такие перечисления строк:

enum Day {
  Sunday = 'Sunday',
  Monday = 'Monday',
  Tuesday = 'Tuesday',
  Wednesday = 'Wednesday',
  Thursday = 'Thursday',
  Friday = 'Friday',
  Saturday = 'Saturday'
}

Я должен признать, что есть функция перечисления, которая не может быть достигнута с помощью типов объединения

Даже если из контекста очевидно, что строковое значение включено в перечисление, вы не можете присвоить его перечислению.

enum StringEnum {
  Foo = 'foo'
}
const foo1: StringEnum = StringEnum.Foo; // no error
const foo2: StringEnum = 'foo'; // error!!

Это унифицирует стиль присвоения значений перечисления по всему коду, исключая использование строковых значений или строковых литералов. Такое поведение не согласуется с тем, как система типов TypeScript ведет себя в других местах, и вызывает удивление, и некоторые люди, считавшие, что это следует исправить, подняли проблемы (this и this) в в котором неоднократно упоминается, что целью перечислений строк является обеспечение «непрозрачных» типов строк: т.е. они могут быть изменены без изменения потребителей.

enum Weekend {
  Saturday = 'Saturday',
  Sunday = 'Sunday'
}
// As this style is forced, you can change the value of
// Weekend.Saturday to 'Sat' without modifying consumers
const weekend: Weekend = Weekend.Saturday;

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

enum Weekend {
  Saturday = 'Saturday',
  Sunday = 'Sunday'
}
// The change of the value of Weekend.Saturday to 'Sat'
// results in a compilation error
const saturday: 'Saturday' = Weekend.Saturday;

Если вы считаете, что эта «непрозрачность» настолько ценна, что можете принять все недостатки, описанные мною выше, взамен на нее, вы не можете отказаться от перечислений строк.

Как удалить перечисления из вашей кодовой базы

С правилом no-limited-syntax ESLint, как описывает.

Тип перечисления не является избыточным, но в большинстве случаев предпочтительнее объединение.

Но не всегда. Использование перечислений для представления, например, переходов между состояниями может быть намного удобнее и выразительнее, чем использование union **

Рассмотрим сценарий реальной жизни:

enum OperationStatus {
  NEW = 1,
  PROCESSING = 2,
  COMPLETED = 4
}

OperationStatus.PROCESSING > OperationStatus.NEW // true
OperationStatus.PROCESSING > OperationStatus.COMPLETED // false

2022 WebDevInsider