programing

ES6를 사용한 Javascript의 열거

starjava 2023. 8. 30. 21:05
반응형

ES6를 사용한 Javascript의 열거

저는 자바스크립트로 오래된 자바 프로젝트를 재구축하고 있는데, 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")
});

객체 참조를 값으로 사용하면 기호와 동일한 충돌 회피를 얻을 수 있습니다.

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

아무 것도 안 보여….

더 좋은 방법이 있습니까?

나는 두 진술을 하나로 묶을 것입니다.

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

보일러 플레이트가 마음에 들지 않으면 반복되는 것처럼Symbol을 쓸 .makeEnum이름 목록에서 동일한 것을 생성합니다.

을 사용하는 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문(그리고 열거형을 확장할 때 스위치 문에 대한 새 대소문자를 잊어버릴 수도 있음)이 예에서는 JSDoc 열거형 주석으로 문서화된 열거형 속성 및 유형도 보여 줍니다.

동일성은 예상대로 작동합니다.Colors.RED === Colors.REDtrue,그리고.Colors.RED === Colors.BLUEfalse.

이것은 저의 개인적인 접근입니다.

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

2020년 5월 11일 업데이트:
정적 필드 및 메서드를 포함하도록 수정되어 "참" 열거형 동작을 보다 가깝게 복제합니다.

업데이트를 계획하고 있다면 "Enum Class"(브라우저 또는 런타임 제한 제외)를 사용해 보는 것이 좋습니다.기본적으로 매우 간단하고 깨끗한 클래스로, 개인 필드와 제한된 액세스 권한을 사용하여 열거형의 동작을 시뮬레이션합니다.이것은 제가 더 많은 기능을 열거형으로 만들고 싶을 때 C#에서 가끔 하는 일입니다.

개인 클래스 필드는 현 시점에서 여전히 실험적이지만 불변 필드/특성을 가진 클래스를 만들기 위한 목적으로 작동하는 것 같습니다.브라우저 지원도 괜찮은 편입니다.그것을 지원하지 않는 유일한 "주요" 브라우저는 파이어폭스(곧 지원할 것이라고 확신함)와 IE(누가 신경 쓰든)입니다.

고지 사항:
저는 개발자가 아닙니다.제가 개인 프로젝트를 진행할 때 JS에 존재하지 않는 열거형의 한계를 해결하기 위해 이것을 정리한 것입니다.

샘플 클래스

class Colors {
    // Private Fields
    static #_RED = 0;
    static #_GREEN = 1;
    static #_BLUE = 2;

    // Accessors for "get" functions only (no "set" functions)
    static get RED() { return this.#_RED; }
    static get GREEN() { return this.#_GREEN; }
    static get BLUE() { return this.#_BLUE; }
}

이제 열거형을 직접 호출할 수 있습니다.

Colors.RED; // 0
Colors.GREEN; // 1
Colors.BLUE; // 2

개인 필드와 제한된 접근자를 사용하면 기존 열거값이 잘 보호됩니다(지금은 기본적으로 상수임).

Colors.RED = 10 // Colors.RED is still 0
Colors._RED = 10 // Colors.RED is still 0
Colors.#_RED = 10 // Colors.RED is still 0

처럼, 은 또한 언이듯또쓸수, 쓸 수 .makeEnum()도우미 기능:

function makeEnum(arr){
    let obj = Object.create(null);
    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");
}

만약 당신이 순수 ES6가 필요하지 않고 Typescript를 사용할 수 있다면, 그것은 좋은 것을 가지고 있습니다.enum:

https://www.typescriptlang.org/docs/handbook/enums.html

TypeScript가 어떻게 실행되는지 확인합니다.기본적으로 다음 작업을 수행합니다.

const MAP = {};

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

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

기호를 사용하거나 물체를 고정하거나 원하는 대로 사용합니다.

ES6 열거형 라이브러리인 Enumify를 확인할 수 있습니다.

다음은 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>


갱신하다

다음은 MDN을 충족하는 보다 최신 버전입니다.

MDN의 권장 사항에 따라 다음과 같이 대체되었습니다.

이 기능은 개체 이니셜라이저 구문을 사용하거나 다음을 사용하여 게터를 정의하기 위해 사용되지 않습니다.Object.defineProperty()API. 이 기능은 널리 구현되어 있지만 레거시 사용 때문에 ECMA스크립트 사양에만 설명되어 있습니다.더 나은 대안이 존재하므로 이 방법을 사용하면 안 됩니다.

편집 : 시제품 추가 (Enum.__prototype 의 JSON 을 지정합니다.

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')
      JSON.stringify(red).should.equal('{"hex":"#F00"}')
    })

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

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

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

  mocha.run()
}

class Enum {
  constructor(...values) {
    this.__values = []

    const [first, ...rest] = values
    const hasOne = rest.length === 0
    const isArray = Array.isArray(first)
    const args = hasOne ? (isArray ? first : Object.keys(first)) : values

    args.forEach((name, index) => {
      this.__createValue({
        name,
        index,
        props: hasOne && !isArray ? first[name] : null
      })
    })

    Object.freeze(this)
  }

  /* @public */
  values() {
    return this.__values
  }

  /* @private */
  __createValue({ name, index, props }) {
    const value = Object.create(Enum.__prototype(props))

    Object.defineProperties(value, Enum.__defineReservedProps({
      name,
      index
    }))

    if (props) {
      Object.defineProperties(value, Enum.__defineAccessors(props))
    }

    Object.defineProperty(this, name, {
      value: Object.freeze(value),
      writable: false
    })

    this.__values.push(this[name])
  }
}

Enum.__prototype = (props) => ({
  toJSON() {
    return props;
  },
  toString() {
    return JSON.stringify(props);
  }
});

/* @private */
Enum.__defineReservedProps = ({ name, index }) => ({
  name: {
    value: Symbol(name),
    writable: false
  },
  ordinal: {
    value: index,
    writable: false
  }
})

/* @private */
Enum.__defineAccessors = (props) =>
  Object.entries(props).reduce((acc, [prop, val]) => ({
    ...acc,
    [prop]: {
      value: val,
      writable: false
    },
    [`get${Enum.__capitalize(prop)}`]: {
      get: () => function() {
        return this[prop]
      }
    }
  }), {})

/* @private */
Enum.__capitalize = (str) => 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>

아마도 이 해결책은? :)

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

여기 있습니다Enum.Symbol.for:

const Enum = (n, ...v) => Object.freeze(v.reduce((o, v) => (o[v] = Symbol.for(`${n}.${v}`), o), {}));

const COLOR = Enum("ACME.Color", "Blue", "Red");
console.log(COLOR.Red.toString());
console.log(COLOR.Red === Symbol.for("ACME.Color.Red"));

또한 es6-dump 패키지(https://www.npmjs.com/package/es6-enum) 도 사용할 수 있습니다.그것은 매우 사용하기 쉽습니다.아래 예를 참조하십시오.

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

저는 ES6/Node.js 생태계의 기초를 더 잘 이해할 수 있도록 약간의 개선과 발굴을 포함한 @tonethar의 접근 방식을 선호합니다.울타리의 서버 쪽에 배경을 두고, 저는 플랫폼의 원시적 요소를 중심으로 한 기능적 스타일의 접근 방식을 선호합니다. 이것은 코드 번짐을 최소화합니다.새로운 유형의 도입으로 인해 죽음의 그림자가 있는 주의 관리 계곡으로 미끄러운 경사가 발생하고 가독성이 증가합니다. 솔루션과 알고리즘의 의도를 보다 명확하게 합니다.

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'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

다음은 몇 가지 도우미 방법을 포함한 제 접근 방식입니다.

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);
const Colors = (function(Colors) {
  Colors[Colors["RED"] = "#f00"] = "RED";
  return Object.freeze(Colors);
})({});
Colors.RED = "#000" // <= Will fail because object is frozen
console.log(Colors.RED); // #f00
console.log(Colors['#f00']); // RED

VSCode / VSCodium과 호환되는 JSDoc으로 증강된 문자열을 사용합니다.효율적이고 간편하며 안전합니다. 예:

/** @typedef { 'red' | 'green' | 'blue' } color */

/** @type {color} */
let color = 'red'

/**
 * @param {color} c
 */
function f(c) {}

ES2022를 사용한 목록에 대한 다른 접근 방식

class Enum {
  static toEnum() {
    const enumMap = new Map();
    for (const [key, value] of Object.entries(this)) {
      enumMap.set(key, value);
    }
    this.enumMap = enumMap;
  }

  static [Symbol.iterator]() {
    return this.enumMap[Symbol.iterator]();
  }

  static getValueOf(str) {
    return this.enumMap.get(str);
  }
}


class ActionTypes extends Enum {
  static REBALANCE = Symbol("REBALANCE");
  static MESSAGE = Symbol("MESSAGE");
  static FETCH = Symbol("FETCH");
  static { this.toEnum() }
}

ES6 지도를 사용할 수 있습니다.

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

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

언급URL : https://stackoverflow.com/questions/44447847/enums-in-javascript-with-es6

반응형