Недавно я использовал Flow, чтобы добавить в JavaScript некоторую проверку статического типа. В этой статье я не буду вдаваться в подробности самого Flow, а расскажу о том, как я подошел к перечислениям.

Это, конечно, не ново, и существует множество библиотек, которые имеют различные преимущества и недостатки. Одна замечательная реализация, которая больше похожа на строгое перечисление, — enumify. Однако без дополнительной ручной подсказки типа Flow и PhpStorm (мой выбор IDE) не могут найти ошибки и разрешить завершение кода соответственно.

Идеальным решением было бы наличие в моей IDE как статического анализа Flow, так и автодополнения кода. Это то, чего я стремился достичь. Но начнем сначала…

Ванильный Javascript

Если вы еще не поняли, перечисления изначально не поддерживаются JavaScript. Это несложная концепция, и многие люди прекрасно справляются с использованием значений в таких объектах, как:

var Color = {
    RED : 0,
    GREEN : 1,
    BLUE : 2
}

Преимущества:

  1. Простой для понимания и используется или долгое время.
  2. Не требует дополнительных библиотек.
  3. Не нуждается в транспиляторах.

Недостатки:

  1. Невозможно проверить тип (что-то вроде typeof color === ‘Color’), поскольку константы могут быть любого типа и не принадлежат родителю или коллекции.
  2. Завершение кода работает только при использовании литералов (например, color === Color.RED), но его нельзя отследить после того, как это значение будет передано переменной. Это важно, когда вы хотите отловить потенциальные ошибки со значениями, которые не являются частью используемого перечисления, как если бы они были. Вручную увидеть или поймать практически невозможно.

Как насчет перечисления?

Как я упоминал ранее, enumify — отличная библиотека и, возможно, наиболее часто используемая поклонниками перечисления:

import {Enum} from 'enumify';
class Color extends Enum {}
Color.initEnum(['RED', 'GREEN', 'BLUE']);

Вы можете узнать больше о функциях здесь, но вывод заключается в том, что он динамически устанавливает переменные экземпляра и их соответствующие значения (в данном случае Color.RED === ‘RED’).

Преимущества:

  1. Он обрабатывает всю беспорядок создания и назначения каждого из перечислений и их значений.
  2. Транспилятор не нужен.
  3. Предоставляет Enumsuperclass методы для безопасного преобразования литералов в перечисления и наоборот.

Недостатки:

  1. Требуется дополнительная библиотека (на самом деле это не имеет большого значения в наши дни).
  2. Нет поддержки завершения кода, так как ваша IDE не будет знать, что REDis является свойством Color.
  3. Нет поддержки Flow по той же причине. Также невозможно поймать нелегалов и всегда ложные условия типа: ‘RED’ === Color.RED
  4. По умолчанию значение основано на имени. Если значение хранится где-то за пределами приложения (например, в базе данных) и имя перечисления изменено (совершенно разумно, поэтому мы абстрагируем магические числа именем), то приложение может нарушить функциональность. Вот почему рекомендуется иметь автоматически сгенерированное уникальное значение, которое будет меняться только в случае изменения порядка перечислений (маловероятно, поскольку большинство инженеров понимают последствия), или вы можете быть более строгим и предоставить значения самостоятельно.

Моя попытка

Я не смог найти ни одной библиотеки, отвечающей моим требованиям. Вот что я придумал:

class Color extends Enum {
  static RED = new Color();
  static GREEN = new Color();
  static BLUE = new Color();
}

К сожалению, на момент написания этой статьи этот синтаксис требует экспериментальных функций. Вы можете избежать этого, используя:

class Color extends Enum {}
Color.RED = new Color();
Color.GREEN = new Color();
Color.BLUE = new Color();

Это менее аккуратно и менее понятно для всех инкапсулированных значений, но JavaScript — это постоянно меняющийся язык, поэтому кто знает, каким будет синтаксис через несколько коротких лет.

Каждому из элементов будет присвоено автоматическое значение, начиная с нуля. Или вы можете указать конструктору явное значение (Color.RED = new Color(‘RED’);)

Color.Blue                        // {"value":2}
Color.Blue == 2                   // true
Color.Blue === 2                  // false
Color.Blue === Color.Blue         // true
Color.Blue === Color.valueOf(2)   // true
Color.Blue === Color.valueOf('2') // false
'Color: ' + Color.Blue            // "Color: 2"
Color.valueOf(3)                  // undefined
Color.Blue.getName()              // “Blue"

Преимущества:

  1. Используя автоматическое значение, вам не нужно явно задавать значения, но вы можете это сделать.
  2. Предоставляет подклассу Enum методы для безопасного преобразования литералов в перечисления и наоборот.
  3. Flow (и интеллектуальные IDE) теперь могут правильно отслеживать типы и значения. Особенно важно для типов параметров и возвращаемых значений.
  4. IDE будут поддерживать завершение кода.

Недостатки:

  1. Требуется дополнительная библиотека (на самом деле это не имеет большого значения в наши дни).
  2. Ему нужен транспилятор для Flow — не проблема, если вы уже используете Flow.
  3. Вы должны быть осторожны при передаче значений (которые являются объектами) туда и обратно с другим ванильным JavsScript.

Вот класс:

class Enum {
  value:number|string;
  constructor(value:number|string = undefined) {
    if (value === undefined) {
      if (typeof this.constructor.prototype.iota === 'undefined') {
        this.constructor.prototype.iota = 0;
      }
      value = this.constructor.prototype.iota++;
    }
    this.value = value;
  }
  getName():string {
    let found = undefined;
    Object.keys(this.constructor).forEach((k:string) => {
      if (this.constructor[k].value === this.value) {
        found = k;
      }
    });
    return found;
  }
  toString():string {
    return `${this.value}`;
  }
  static valueOf(value:number|string):undefined|this {
    let found = undefined;
    Object.keys(this).forEach((k:string) => {
      if (this[k].value === value) {
        found = this[k];
      }
    });
    return found;
  }
}

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

Первоначально опубликовано на http://elliot.land 20 августа 2016 г.