Перечисления в 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);
Ответ 7
Если вам не нужен чистый ES6 и он может использовать TypScript, у него есть хороший enum
:
https://www.typescriptlang.org/docs/handbook/enums.html
Ответ 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'));