JavaScript/TypeScript

TypeScript | Enum 열거형 타입

pathas 2020. 5. 6. 15:13

Enum 열거형 타입

열거형 타입은 enum 키워드를 사용해서 정의
열거형 타입의 각 원소는 값 또는 타입으로 사용 가능

// 열거형 타입으로 과일 정의
enum Fruit {
    Apple,
    Banana,
    Orange
}

// 열거형 타입의 원소인 Apple을 값으로 사용
const v1: Fruit = Fruit.Apple;

// 열거형 타입의 원소인 Apple, Banana를 타입으로 사용
const v2: Fruit.Apple | Fruit.Banana = Fruit.Banana;

enum 원소의 값

enum Fruit {
    Apple,
    Banana = 5,
    Orange
}

console.log(Fruit.Apple, Fruit.Banana, Fruit.Orange);
// 0 5 6
  • 열거형 타입의 첫 번째 원소에 값을 할당하지 않으면 자동으로 0 할당
  • 열거형 타입의 각 원소에 숫자 또는 문자열 할당 가능
    명시적으로 값을 입력하지 않으면 이전 원소에서 1만큼 증가한 값 할당

객체로 존재하는 열거형 타입

// 타입스크립트는 위의 코드를 아래와 같이 컴파일
var Fruit;
(function(Fruit){
    Fruit[(Fruit['Apple'] = 0)] = 'Apple';
    Fruit[(Fruit['Banana'] = 5)] = 'Banana';
    Fruit[(Fruit['Orange'] = 6)] = 'Orange';
})(Fruit || (Fruit = {}));
console.log(Fruit.Apple, Fruit.Banana, Fruit.Orange);
// 0, 5, 6
  • 열거형 타입은 객체로 존재
  • 열거형 타입의 각 원소는 이름과 값이 양방향으로 매핑(mapping)
  • 열거형 타입은 객체로 존재하기 때문에 해당 객체를 런타임에 사용 가능

열거형 타입 객체 사용하기

enum Fruit {
    Apple,
    Banana = 5,
    Orange
}
console.log(Fruit.Banana); // 5
console.log(Fruit['Banana']); // 5
console.log(Fruit[5]); // Banana
  • 열거형 타입은 객체이기 때문에 일반적인 객체처럼 다룰 수 있음
  • 각 원소의 이름과 값이 양방향으로 매핑되어 있기 때문에 값을 이용해서 이름을 가져올 수 있음
    단, 서로 다른 원소에 같은 수를 할당하는 경우 마지막 원소의 이름을 반환

열거형 타입의 값으로 문자열 할당

enum Language {
    Korean = 'ko',
    English = 'en',
    Japanese = 'jp'
}

console.log(Language.Korean); // ko
console.log(Language['ko']); // 에러 발생

// 컴파일 결과
"use strict";
var Language;
(function (Language) {
    Language["Korean"] = "ko";
    Language["English"] = "en";
    Language["Japanese"] = "jp";
})(Language || (Language = {}));
  • 열거형 타입의 원소에 문자열을 할당하는 경우 단방향으로 매핑됨
  • 이는 서로 다른 원소의 이름 또는 값이 같을 경우 충돌이 발생하기 때문
    → 컴파일 결과를 보면 위에서 봤던 컴파일 결과와 매핑 형식이 다름을 알 수 있음

열거형 타입을 위한 유틸리티 함수

열거형 타입을 자주 사용한다면 몇 가지 유틸리티 함수를 만들어서 사용하는 것이 좋음

열거형 타입의 원소 개수를 알려 주는 함수

function getEnumLength(enumObject: any) {
    const keys = Object.keys(enumObject);
    return keys.reduce(
        // enum 원소의 값이 숫자이면 2씩 증가하므로 문자열만 계산
        (acc, key) => (typeof enumObject[key] === 'string' ? acc + 1 : acc), 0
    )
}
  • 원소의 값이 숫자인 경우에는 양방향으로 매핑되기 때문에 주의해야 함
  • 객체의 속성값이 문자열인 경우만 계산하면 열거형 타입에서 원소의 개수를 구할 수 있음

열거형 타입에 존재하는 값인지 검사하는 함수

function isValidEnumValue(enumObject: any, value: number | string) {
    if(typeof value === 'number'){
        return !!enumObject[value];
    } else {
        return (
        Object.keys(enumObject)
        .filter(key => isNaN(Number(key)))
        .find(key => enumObject[key] === value) != null
            )
    }
}
  • 찾으려는 값이 숫자이면 양방향 매핑을 이용해서 검사
    !! → 값이 있는 경우 true를 반환하기 위함
  • 찾으려는 값이 문자열이면 양방향 매핑에 의해 생성된 키를 제거하고 해당 값이 존재하는지 검사
  • find 메서드에서 !=(엄격하지 않은 비교)를 사용하는 이유는 undefined를 가려내기 위함
    undefined !== null은 true를 반환

간단한 사용 예제

enum Fruit {
    Apple,
    Banana,
    Orange
}

enum Language {
    Korean = 'ko',
    English = 'en',
    Japanese = 'jp'
}

console.log(getEnumLength(Fruit), getEnumLength(Language)); // 3 3
console.log('1 in Fruit', isValidEnumValue(Fruit, 1)); // true
console.log('5 in Fruit', isValidEnumValue(Fruit, 5)); // false
console.log('ko in Language', isValidEnumValue(Language, 'ko')); // true
console.log('Korean in Language', isValidEnumValue(Language, 'Korean')); // false

실용적 활용 예제

isValidEnum 함수는 서버로부터 받은 데이터를 검증할 때 사용 가능

getEnumLength 함수는 다음과 같이 실수 방지 용도로 사용 가능

enum Fruit {
    Apple,
    Banana,
    Orange
}

// 과일의 가격 정보를 가진 객체
const FRUIT_PRICE = {
    [Fruit.Apple]: 1000,
    [Fruit.Banana]: 1500,
    [Fruit.Orange]: 2000
}

// 이하 코드는 jest와 같은 테스팅 프레임 워크 필요
test('FRUIT_PRICE에 정의되지 않은 Fruit가 있는지 체크' () => {
    expect(getEnumLength(Fruit)).toBe(Object.keys(FRUIT_PRICE).length);
});
  • 과일 가격 정보 객체는 Fruit의 모든 원소를 갖고 있어야 함
  • 따라서 Fruit에 새로운 과일이 추가되면 FRUIT_PRICE에도 반드시 해당 과일의 가격을 추가해야 함
  • getEnumLength 함수를 이용해서 테스트 코드를 작성하면 Fruit와 FRUIT_PRICE의 크기를 항상 같게 유지할 수 있음

상수 열거형 타입

열거형 타입은 컴파일 후에도 남아 있기 때문에 번들 파일의 크기가 불필요하게 커질 수 있음
상수(const) 열거형 타입을 사용하면 컴파일 결과에 열거형 타입의 객체를 남겨 놓지 않을 수 있음

const enum Fruit {
    Apple,
    Banana,
    Orange,
}
const fruit: Fruit = Fruit.Apple;

const enum Language {
    Korean = 'ko',
    English = 'en',
    Japanese = 'jp',
}
const lang: Language = Language.Korean;

console.log(getEnumLength(Fruit)); // 컴파일 에러 발생

// 컴파일 결과
"use strict";
const fruit = 0 /* Apple */;
const lang = "ko" /* Korean */;
  • 컴파일 결과에 열거형 타입의 객체를 생성하는 코드가 보이지 않음
  • 열거형 타입이 사용된 코드는 원소의 값으로 대체되므로 코드가 간소화됨
  • 하지만 상수 열거형 타입을 사용하면 열거형 타입 객체 사용 불가

참고도서: [실전 리액트 프로그래밍 / 저자_ 이재승 / 출판사_ 프로그래밍 인사이트]