ES6でのJavaScriptの列挙型


136

私はJavascriptで古いJavaプロジェクトを再構築していて、JSで列挙型を行う良い方法がないことに気づきました。

私が思いつくことができる最高のものは:

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

const続けてColors再割り当てされているから、それがキーと値を変異防止凍結します。私はシンボルを使用しているので、それColors.REDはと等しくない0か、それ以外のものは何でもありません。

この処方に問題はありますか?もっと良い方法はありますか?


(私はこの質問が少し繰り返されることを知っていますが、以前のすべてのQ / Aはかなり古く、ES6はいくつかの新しい機能を提供します。)


編集:

シリアル化の問題を処理する別の解決策ですが、まだレルムの問題があると思います。

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

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

値としてオブジェクト参照を使用することにより、シンボルと同じ衝突回避が得られます。


2
これはes6の完璧なアプローチです。あなたはそれを凍結する必要はありません
Nirusは2017年

2
@Nirus、変更したくない場合。
zerkms 2017年

2
この答えに気づきましたか?
Bergi、2017年

3
私が考えることができる1つの問題:でこの列挙型を使用できませんJSON.stringify()。シリアライズ/デシリアライズできませんSymbol
le_m

1
@ErictheRed私は何年もの間、文字列列挙定数値を問題なく使用してきました。フロー(またはTypeScript)を使用すると、衝突回避に関するフレッティングよりもはるかに高い型安全性が保証されるためです
Andy

回答:


131

この処方に問題はありますか?

何も見えません。

もっと良い方法はありますか?

2つのステートメントを1つにまとめます。

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

繰り返しのSymbol呼び出しのようにボイラープレートが気に入らない場合は、makeEnum名前のリストから同じものを作成するヘルパー関数を作成することもできます。


3
ここに領域の問題はありませんか?

2
@torazaburoつまり、コードが2回読み込まれると、異なるシンボルが生成されますが、これは文字列の問題ではありませんか?ええ、良い点、それを答えにしてください:-)
Bergi

2
@ErictheRedいいえ、レルム間の問題Symbol.forはありませが、真にグローバルな名前空間との通常の衝突問題があります
ベルギ

1
@ErictheRedいつ、どこから(どのレルム/フレーム/タブ/プロセスから)呼び出されても、まったく同じシンボルが作成されることが保証されています
Bergi

1
@jamesemanon必要に応じて説明取得できますが、主にデバッグのみに使用します。むしろ、いつものようにカスタムの列挙型から文字列への変換関数(行に沿ったものenum => ({[Colors.RED]: "bright red", [Colors.BLUE]: "deep blue", [Colors.GREEN]: "grass green"}[enum]))を持っています。
ベルギ

18

使用しながら、Symbol列挙型の値は、単純なユースケースのために正常に動作として、列挙型にプロパティを与えるために便利です。これは、Object、プロパティを含む列挙値としてます。

たとえば、それぞれにColors名前と16進値を与えることができます:

/**
 * 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" }
});

enumにプロパティを含めることで、switchステートメントを記述する必要がなくなります(enumが拡張されたときに、switchステートメントへの新しいケースを忘れることもありません)。この例は、JSDoc enumアノテーションで文書化されたenumのプロパティと型も示しています。

平等Colors.RED === Colors.REDtrue、存在し、Colors.RED === Colors.BLUE存在することで期待どおりに機能しfalseます。


9

上記のように、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");
}

2
ワンライナーとして:次の const makeEnum = (...lst) => Object.freeze(Object.assign({}, ...lst.map(k => ({[k]: Symbol(k)})))); ように使用します const colors = makeEnum("Red", "Green", "Blue")
Manuel Ebert

9

これは私の個人的なアプローチです。

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);

これを使用することはお勧めしません。すべての可能な値を反復処理する方法がなく、値をColorTypeであるかどうかを手動で確認せずに確認する方法がないためです。
ドミノ

7

TypeScriptの動作を確認してください。基本的に彼らは次のことを行います:

const MAP = {};

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

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

シンボルを使用し、オブジェクトをフリーズします。


MAP[MAP[1] = 'A'] = 1;代わりにを使用する理由については説明しませんMAP[1] = 'A'; MAP['A'] = 1;。代入を式として使うのは悪いスタイルだといつも聞いていました。また、ミラーリングされた割り当てからどのようなメリットがありますか?
エリックザレッド

1
列挙型マッピングがドキュメントでes5にコンパイルされる方法へのリンクです。typescriptlang.org/docs/handbook/enums.html#reverse-mappings 私が想像できるように、1行にコンパイルする方が簡単で簡潔になりますMAP[MAP[1] = 'A'] = 1;
Givehug

ええと。したがって、ミラーリングにより、各値の文字列表現と数値/シンボル表現を簡単に切り替えられ、を実行して、一部の文字列または数値/シンボルxが有効なEnum値であることを確認できるようになりEnum[Enum[x]] === xます。それは私の元の問題のいずれも解決しませんが、役に立つ可能性があり、何も壊しません。
エリックザレッド

1
TypeScriptは、TSコードがコンパイルされると失われる堅牢性の層を追加することに注意してください。アプリ全体がTSで記述されている場合はすばらしいですが、JSコードを堅牢にしたい場合は、シンボルの凍結されたマップがより安全なパターンのように聞こえます。
ドミノ



1

多分このソリューション?:)

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

例:

createEnum(['red', 'green', 'blue']);

> {RED: Symbol(red), GREEN: Symbol(green), BLUE: Symbol(blue)}

使用例は本当にありがたいです:-)
Abderrahmane TAHRI JOUTI '29

0

ES6 / Node.jsエコシステムの基礎をよりよく理解するために、@ tonetharのアプローチを少し強化して掘り下げています。フェンスのサーバー側に背景があるので、プラットフォームのプリミティブの周りの機能的なスタイルのアプローチを好みます。これにより、コードの肥大化、新しいタイプの導入による死の影の州の管理谷への滑りやすい勾配が最小限に抑えられ、増加します。可読性-ソリューションとアルゴリズムの意図をより明確にします。

TDDES6Node.jsLodashJestによるソリューションバベル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's 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

Array.from(Object.assign(args))まったく何もしません。...args直接使用することもできます。
ドミノ

0

ここにいくつかのヘルパーメソッドを含む私のアプローチがあります

export default class Enum {

    constructor(name){
        this.name = name;
    }

    static get values(){
        return Object.values(this);
    }

    static forName(name){
        for(var enumValue of this.values){
            if(enumValue.name === name){
                return enumValue;
            }
        }
        throw new Error('Unknown value "' + name + '"');
    }

    toString(){
        return this.name;
    }
}

-

import Enum from './enum.js';

export default class ColumnType extends Enum {  

    constructor(name, clazz){
        super(name);        
        this.associatedClass = clazz;
    }
}

ColumnType.Integer = new ColumnType('Integer', Number);
ColumnType.Double = new ColumnType('Double', Number);
ColumnType.String = new ColumnType('String', String);

0

es6-enumパッケージを使用することもできます(https://www.npmjs.com/package/es6-enum)。使い方はとても簡単です。以下の例をご覧ください。

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

10
以下の例はどれですか?
Alexander

あなたが例を挙げれば、人々はあなたの答えに投票します。
Artem Fedotov

0

これが、JavaScriptでのJava列挙の実装です。

単体テストも含めました。

const main = () => {
  mocha.setup('bdd')
  chai.should()

  describe('Test Color [From Array]', function() {
    let Color = new Enum('RED', 'BLUE', 'GREEN')
    
    it('Test: Color.values()', () => {
      Color.values().length.should.equal(3)
    })

    it('Test: Color.RED', () => {
      chai.assert.isNotNull(Color.RED)
    })

    it('Test: Color.BLUE', () => {
      chai.assert.isNotNull(Color.BLUE)
    })

    it('Test: Color.GREEN', () => {
      chai.assert.isNotNull(Color.GREEN)
    })

    it('Test: Color.YELLOW', () => {
      chai.assert.isUndefined(Color.YELLOW)
    })
  })

  describe('Test Color [From Object]', function() {
    let Color = new Enum({
      RED   : { hex: '#F00' },
      BLUE  : { hex: '#0F0' },
      GREEN : { hex: '#00F' }
    })

    it('Test: Color.values()', () => {
      Color.values().length.should.equal(3)
    })

    it('Test: Color.RED', () => {
      let red = Color.RED
      chai.assert.isNotNull(red)
      red.getHex().should.equal('#F00')
    })

    it('Test: Color.BLUE', () => {
      let blue = Color.BLUE
      chai.assert.isNotNull(blue)
      blue.getHex().should.equal('#0F0')
    })

    it('Test: Color.GREEN', () => {
      let green = Color.GREEN
      chai.assert.isNotNull(green)
      green.getHex().should.equal('#00F')
    })

    it('Test: Color.YELLOW', () => {
      let yellow = Color.YELLOW
      chai.assert.isUndefined(yellow)
    })
  })

  mocha.run()
}

class Enum {
  constructor(values) {
    this.__values = []
    let isObject = arguments.length === 1
    let args = isObject ? Object.keys(values) : [...arguments]
    args.forEach((name, index) => {
      this.__createValue(name, isObject ? values[name] : null, index)
    })
    Object.freeze(this)
  }

  values() {
    return this.__values
  }

  /* @private */
  __createValue(name, props, index) {
    let value = new Object()
    value.__defineGetter__('name', function() {
      return Symbol(name)
    })
    value.__defineGetter__('ordinal', function() {
      return index
    })
    if (props) {
      Object.keys(props).forEach(prop => {
        value.__defineGetter__(prop, function() {
          return props[prop]
        })
        value.__proto__['get' + this.__capitalize(prop)] = function() {
          return this[prop]
        }
      })
    }
    Object.defineProperty(this, name, {
      value: Object.freeze(value),
      writable: false
    })
    this.__values.push(this[name])
  }

  /* @private */
  __capitalize(str) {
    return str.charAt(0).toUpperCase() + str.slice(1)
  }
}

main()
.as-console-wrapper {
  top: 0;
  max-height: 100% !important;
}
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.2.5/mocha.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.2.5/mocha.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.2.0/chai.js"></script>
<!--

public enum Color {
  RED("#F00"),
  BLUE("#0F0"),
  GREEN("#00F");
  
  private String hex;
  public String getHex()  { return this.hex;  }
  
  private Color(String hex) {
    this.hex = hex;
  }
}

-->
<div id="mocha"></div>


-3

ES6マップを使用できます

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

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

私見は、それが使いそう...(ミューテータメソッドを呼び出し、任意のキーの値を変更することができます)列挙自然のとсontradiction(アクセサメソッドたびに呼び出す必要があります)、その複雑さの悪い解決策をだconst x = Object.freeze({key: 'value'})何かを得るために、代わりにそのルックスとES6の列挙型のように動作します
Yurii Rabeshko '22

値を取得するには、colors.get( 'RED')の場合と同様に、文字列を渡す必要があります。エラーが発生しやすいです。
エイドリアン・オビエド
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.