Перечисления в Javascript с ES6

Я перестраиваю старый Java-проект в Javascript и понимаю, что нет хорошего способа сделать перечисления в JS.

Лучшее, что я могу придумать, это:

const Colors = {
    RED: Symbol("red"),
    BLUE: Symbol("blue"),
    GREEN: Symbol("green")
};
Object.freeze(Colors);

const сохраняет Colors от быть переназначены, и замораживание он предотвращает мутирует ключи и значения. Я использую символы, чтобы Colors.RED не был равен 0 или что-то еще, кроме самого себя.

Есть ли проблема с этой формулировкой? Есть ли способ лучше?


(Я знаю, что этот вопрос немного повторен, но все предыдущие Q/As довольно старые, а ES6 дает нам некоторые новые возможности.)


РЕДАКТИРОВАТЬ:

Еще одно решение, которое касается проблемы сериализации, но я считаю, что все еще есть проблемы в области:

const enumValue = (name) => Object.freeze({toString: () => name});

const Colors = Object.freeze({
    RED: enumValue("Colors.RED"),
    BLUE: enumValue("Colors.BLUE"),
    GREEN: enumValue("Colors.GREEN")
});

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

Ответы

Ответ 1

Есть ли проблема с этой формулировкой?

Я не вижу.

Есть ли лучший способ?

Я бы свести два утверждения в один:

const Colors = Object.freeze({
    RED:   Symbol("red"),
    BLUE:  Symbol("blue"),
    GREEN: Symbol("green")
});

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

Ответ 2

Хотя использование Symbol в качестве значения перечисления прекрасно работает для простых случаев использования, может быть удобно задать свойства перечислениям. Это можно сделать, используя Object в качестве значения перечисления, содержащего свойства.

Например, мы можем дать каждому из Colors имя и шестнадцатеричное значение:

/**
 * Enum for common colors.
 * @readonly
 * @enum {{name: string, hex: string}}
 */
const Colors = Object.freeze({
  RED:   { name: "red", hex: "#f00" },
  BLUE:  { name: "blue", hex: "#00f" },
  GREEN: { name: "green", hex: "#0f0" }
});

Включение свойств в перечисление избавляет от необходимости записывать операторы switch (и, возможно, забывать новые случаи в операторах switch при расширении перечисления). В примере также показаны свойства и типы перечисления, задокументированные с помощью аннотации перечисления JSDoc.

Равенство работает, как и ожидалось, с Colors.RED === Colors.RED true, и Colors.RED === Colors.BLUE false.

Ответ 3

Как уже упоминалось выше, вы также можете написать вспомогательную функцию makeEnum():

function makeEnum(arr){
    let obj = {};
    for (let val of arr){
        obj[val] = Symbol(val);
    }
    return Object.freeze(obj);
}

Используйте его так:

const Colors = makeEnum(["red","green","blue"]);
let startColor = Colors.red; 
console.log(startColor); // Symbol(red)

if(startColor == Colors.red){
    console.log("Do red things");
}else{
    console.log("Do non-red things");
}

Ответ 4

Проверьте, как это делает TypeScript. В основном они делают следующее:

const MAP = {};

MAP[MAP[1] = 'A'] = 1;
MAP[MAP[2] = 'B'] = 2;

MAP['A'] // 1
MAP[1] // A

Используйте символы, объект замораживания, все, что захотите.

Ответ 5

Вы можете проверить Enumify, очень хорошую и хорошо оснащенную библиотеку для перечислений ES6.

Ответ 6

Это мой личный подход.

class ColorType {
    static get RED () {
        return "red";
    }

    static get GREEN () {
        return "green";
    }

    static get BLUE () {
        return "blue";
    }
}

// Use case.
const color = Color.create(ColorType.RED);

Ответ 8

Может быть, это решение? :)

function createEnum (array) {
  return Object.freeze(array
    .reduce((obj, item) => {
      if (typeof item === 'string') {
        obj[item] = Symbol(item)
      }
      return obj
    }, {}))
}

Ответ 9

Я создал собственный класс Enum. Надеюсь, это может помочь вам :)

class Enum {
  constructor(values, method) {
    this.initialValues = values;
    Object.keys(values).forEach(v => {
      this[v] = { rawValue: values[v] };

      if (method) {
        const methods = method(this[v]);
        Object.keys(methods).forEach(methodName => {
          this[v][methodName] = methods[methodName];
        });
      }
    });

    Object.freeze(this);
  }

  for(rawValue) {
    const key = Object.keys(this.initialValues).find(key => 
      this.initialValues[key] === rawValue
    );
    return this[key];
  }
}

ИСПОЛЬЗОВАНИЕ:

const PostType = new Enum({
  POST: "post",
  YOUTUBE: "youtube",
  LINK: "link",
}, (self) => ({
  print: () => {
    console.log(self.rawValue);
  },
}));

console.log(PostType.POST.rawValue); // "post"
PostType.LINK.print(); // "link"
PostType.for("youtube").print(); // "youtube"

Ответ 10

Я предпочитаю подход @tonethar с небольшим количеством улучшений и копаний для лучшего понимания основ экосистемы ES6/Node.js. На фоне серверной части забора я предпочитаю подход функционального стиля вокруг примитивов платформы, это минимизирует раздувание кода, скользкий уклон в долину управления состоянием тени смерти из-за введения новых типов и увеличения удобочитаемость - проясняет цель решения и алгоритм.

Решение с помощью TDD, ES6, Node.js, Lodash, Jest, Babel, ESLint

// ./utils.js
import _ from 'lodash';

const enumOf = (...args) =>
  Object.freeze( Array.from( Object.assign(args) )
    .filter( (item) => _.isString(item))
    .map((item) => Object.freeze(Symbol.for(item))));

const sum = (a, b) => a + b;

export {enumOf, sum};
// ./utils.js

// ./kittens.js
import {enumOf} from "./utils";

const kittens = (()=> {
  const Kittens = enumOf(null, undefined, 'max', 'joe', 13, -13, 'tabby', new 
    Date(), 'tom');
  return () => Kittens;
})();

export default kittens();
// ./kittens.js 

// ./utils.test.js
import _ from 'lodash';
import kittens from './kittens';

test('enum works as expected', () => {
  kittens.forEach((kitten) => {
    // in a typed world, do your type checks...
    expect(_.isSymbol(kitten));

    // no extraction of the wrapped string here ...
    // toString is bound to the receiver type
    expect(kitten.toString().startsWith('Symbol(')).not.toBe(false);
    expect(String(kitten).startsWith('Symbol(')).not.toBe(false);
    expect(_.isFunction(Object.valueOf(kitten))).not.toBe(false);

    const petGift = 0 === Math.random() % 2 ? kitten.description : 
      Symbol.keyFor(kitten);
    expect(petGift.startsWith('Symbol(')).not.toBe(true);
    console.log('Unwrapped Christmas kitten pet gift '${petGift}', yeee :) 
    !!!');
    expect(()=> {kitten.description = 'fff';}).toThrow();
  });
});
// ./utils.test.js

Ответ 11

Вы также можете использовать пакет es6-enum (https://www.npmjs.com/package/es6-enum). Это очень просто в использовании. Смотрите пример ниже:

import Enum from "es6-enum";
const Colors = Enum("red", "blue", "green");

Colors.red возвращает Symbol(red)

Ответ 12

Вы можете использовать ES6 Map

const colors = new Map([
  ['RED', 'red'],
  ['BLUE', 'blue'],
  ['GREEN', 'green']
]);

console.log(colors.get('RED'));