ES6을 사용하는 Javascript의 열거 형


136

Javascript에서 오래된 Java 프로젝트를 다시 작성 중이며 JS에서 열거 형을 수행하는 좋은 방법이 없다는 것을 깨달았습니다.

내가 생각해 낼 수있는 최선은 다음과 같습니다.

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

const계속 Colors재 할당되는 것을, 그리고이 키와 값을 돌연변이 방지 동결. 나는 Symbols를 사용하여 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

2
당신이 그것을 수정하고 싶지 않으면 @Nirus.
zerkms 2012 년

2
이 답변을 보셨습니까 ?
Bergi

3
내가 생각할 수있는 한 가지 문제 :이 열거 형을 사용할 수 없습니다 JSON.stringify(). 직렬화 / 역 직렬화 할 수 없습니다 Symbol.
le_m 2016 년

1
@ErictheRed 흐름 (또는 TypeScript)을 사용하면 충돌 회피에 대한 프레 팅보다 더 많은 유형 안전을 보장하기 때문에 수년간 문자열 열거 형 상수 값을 사용했습니다.
앤디

답변:


131

이 제제에 문제가 있습니까?

나는 보이지 않는다.

더 좋은 방법이 있습니까?

두 문장을 하나로 축소했습니다.

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

반복 Symbol호출 과 같이 상용구가 마음에 들지 않으면 makeEnum이름 목록에서 동일한 것을 만드는 도우미 함수 를 작성할 수도 있습니다.


3
여기에 영역 문제가 없습니까?

2
@torazaburo 코드가 두 번로드되면 다른 기호가 생성되어 문자열에 문제가되지 않습니까? 그래, 좋은 점은, 그 대답 :-) 만들기
BERGI

2
@ErictheRed 아니오, Symbol.for않습니다 하지 상호 영역의 문제가, 그러나 그것은을 가진 보통의 충돌 문제를 가지고 진정한 글로벌 네임 스페이스를 .
Bergi

1
@ErictheRed 실제로 언제 어떤 장소에서 어떤 영역 / 프레임 / 탭 / 프로세스가 호출되는지에 관계없이 동일한 심볼을 생성 할 수 있습니다.
Bergi

1
@jamesemanon 원하는 경우 설명을 얻을 수 있지만 주로 디버깅에만 사용합니다. 오히려 평소와 같이 사용자 정의 열거 형에서 문자열로 변환 기능이 있습니다 (줄을 따라 무언가 enum => ({[Colors.RED]: "bright red", [Colors.BLUE]: "deep blue", [Colors.GREEN]: "grass green"}[enum])).
Bergi

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

열거 형에 속성을 포함하면 switch명령문을 작성할 필요가 없으며 열거 형이 확장 될 때 switch 명령문에 대한 새로운 사례를 잊어 버릴 수 있습니다. 이 예제는 JSDoc 열거 형 주석으로 문서화 된 열거 형 속성 및 유형도 보여줍니다. .

평등은 Colors.RED === Colors.RED존재 true하고 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;. 나는 항상 표현으로 표현을 사용하는 것이 나쁜 스타일이라고 들었습니다. 또한 미러링 된 할당에서 어떤 이점이 있습니까?
Eric the Red

1
다음은 문서에서 enum 매핑이 es5로 컴파일되는 방법에 대한 링크입니다. typescriptlang.org/docs/handbook/enums.html#reverse-mappings 예를 들어 한 줄로 컴파일하는 것이 더 쉽고 간결 할 수 있습니다 MAP[MAP[1] = 'A'] = 1;.
givehug

허. 따라서 미러링은 각 값의 문자열 및 숫자 / 기호 표현 간을 쉽게 전환 x하고을 수행하여 일부 문자열 또는 숫자 / 기호 가 유효한 Enum 값 인지 확인하는 것처럼 보입니다 Enum[Enum[x]] === x. 그것은 원래의 문제를 해결하지 못했지만 유용 할 수 있으며 아무것도 깨지 않습니다.
Eric the Red

1
TypeScript는 TS 코드가 컴파일되면 손실되는 견고성을 제공합니다. 전체 앱을 TS로 작성하면 훌륭하지만 JS 코드를 강력하게 유지하려면 고정 된 심볼 맵이 더 안전한 패턴처럼 들립니다.
도미노


4

ES6 열거 형을위한 매우 훌륭하고 기능이 뛰어난 라이브러리 인 Enumify 를 확인할 수 있습니다 .


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

사용법 예는 정말 :-) 감사하겠습니다
압 데라 타리 JOUTI에게

0

ES6 / Node.js 생태계의 기초를 더 잘 이해하는 이점을 위해 약간의 개선 및 파기 기능을 갖춘 @tonethar의 접근 방식을 선호합니다. 펜스의 서버쪽에 배경을두고 플랫폼의 기본 요소에 대한 기능적 스타일의 접근 방식을 선호합니다. 가독성-솔루션의 의도와 알고리즘을보다 명확하게합니다.

와 솔루션 TDD , ES6 , Node.js를 , Lodash , 농담 , 바벨 , 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 Map을 사용할 수 있습니다

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

colors.get ( 'RED')처럼 값을 얻으려면 문자열을 전달해야합니다. 오류가 발생하기 쉽습니다.
adrian oviedo
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.