안녕하세요, 단테입니다.
오늘은 typescript enum에 대한 이야기를 나눠보려고 합니다.
enum에 대한 사람들의 인식은 마냥 호의적이진 않은 것 같습니다.
어떠한 점 때문에 그런지 한번 알아보겠습니다.
enum은 숫자형 (numberic enums), 문자열형(string enums)로 나뉘어 집니다.
먼저 string 타입을 참조하기 위해 다음과 같이 enum을 선언하는 경우를 예시로 들겠습니다.
enum NamePolicy {
phoneNumber = 'phoneNumber',
countryCode = 'countryCode',
tag = 'tag',
ssn = 'ssn'
}
선언한 NamePolicy를 함수 인자에서 활용하려고 하면 아래처럼 의도에 맞게 자동완성이 되는데요
인자에 명시적으로 enum 값이 아닌 "phoneNumber"를 넣었을 때는 아래와 같이 에러를 발생합니다.
object literal 형식일 경우 "phoneNumber"도 인자로 선언할 수 있는데요, enum은 다르게 동작함을 알 수 있습니다.
바로 위의 상황에서 너그럽게 phoneNumber를 인자로 받아들여지지 않게 하는 것이 의도였든 의도이지 않았든 NamePolicy 값인 phoneNumber
, countryCode
, tag
, ssn
만 getNamePolicy
의 인자로 사용할 수 있다는 것은 최소한 보장이 되었습니다.
그런데 말입니다.
다음처럼 임의의 number 타입이 NamePolicy에 들어가는 경우를 보겠습니다.
enum NamePolicy {
phoneNumber = 'phoneNumber',
countryCode = 'countryCode',
tag = 'tag',
ssn = 'ssn',
}
console.log(Object.keys(NamePolicy)) // ["phoneNumber", "countryCode", "tag", "ssn"]
enum NamePolicy {
phoneNumber = 'phoneNumber',
countryCode = 'countryCode',
tag = 'tag',
ssn = 'ssn',
pollute = 1
}
console.log(Object.keys(NamePolicy)) // ["1", "phoneNumber", "countryCode", "tag", "ssn", "pollute"]
Object.keys, Object.values의 반환 값에 pollute, 1이 함께 포함되는 것을 볼 수 있습니다. 예상치 못한 결과입니다.
After의 NamePolicy 경우처럼 string과 number 형을 함께 사용하는 enum을 이종 열거형(Heterogeneous enum)이라고 부르는데요, Object.values를 사용하지 않으면 되지 않나라는 생각이 들 수 있지만 함수 호출 부에서도 문제가 발생합니다.
pollute가 enum 값으로 들어가버린 후로 getNamePolicy는 1 뿐만 아니라 그 어떤 숫자형도 런타임에 인자값으로 받아들일 수 있게 되었습니다.
그 누구도 NamePolicy에 number 타입을 넣는 것을 막을 수 없습니다.
만약 숫자형만 사용한다고 하더라도 직렬화에서 문제가 발생합니다.
enum RainbowColorCode {
red, // 0
orange, // 1
yellow,
green,
blue
}
무지개 색을 순서대로 값에 매핑시켰을 때 red는 0, orange는 1... 이런 식으로 되는데요,
red와 orange 사이에 누가 purple을 넣게 되면 orange 부터는 기존 값보다 1씩 증가된 형태가 됩니다.
enum RainbowColorCode {
red, // 0
purple, // 1
orange, // 2
yellow,
green,
blue
}
RainbowColorCode.Orange // 이제 이 오렌지는 2입니다.
숫자형 값을 사용하기 위한 직렬화에 enum을 사용한다면 이렇게 예상치 못한 경우를 막을 수가 없습니다.
코드 리뷰에서도 무심코 건너가버릴 수 있겠죠.
위의 문제를 예방하기 위해서 object literral
형식을 사용할 수 있습니다.
const rainbowColorCode = {
red: 0,
purple: 1,
orange: 2,
yellow: 3,
green: 4,
blue: 5
}
type RainbowColorCodeValue = typeof rainbowColorCode[keyof typeof rainbowColorCode];
function getColor(code: RainbowColorCodeValue) {
console.log(code);
}
getColor
의 파라메터 타입은 RainbowColorCodeValue
로 선언되어있지만 RainbowColorCodeValue
은 number 타입이기 때문에 getColor(10)
와같이 [0..5] 이 아닌 다른 number 타입을 넣을 수 있습니다.
위 상황을 피하기 위해서
3.4 version 이상부터 지원되는 const assertion
를 사용합니다.
앞서 선언한 rainbowColorCode
에서 key, value만 타입으로 추출해 사용할 수 있습니다.
type RainbowColorCodeValue = typeof rainbowColorCode[keyof typeof rainbowColorCode];
function getColor(code: RainbowColorCodeValue) {
console.log(code);
}
type RainbowColorCodeKey = keyof typeof rainbowColorCode;
function pickColorValue(codeKey: RainbowColorCodeKey) {
console.log(rainbowColorCode[codeKey])
}
keyof typeof rainbowColorCode;
과 같이 object literal key 값 타입 추출은 string union 타입과 비교했을 때 어떤 장점이 있을까요?
type RainbowColorCodeKey = "red" | "purple" | "orange" | "yellow"
string union 타입을 사용했을 때 IDE 에서 RainbowColorCodeKey
를 사용한 흔적을 찾으려면 어떻게 해야할까요?
"red" | "purple" | "orange" | "yellow"
의 각 글자를 검색해야 할 것입니다.
이것 보다는 object literal 형식으로 작성된 rainbowColorCode
를 검색하는 것이 더 찾기 쉽고, 각 키 값에 매핑되는 값을 사용할 때도 IDE의 자동완성 기능을 사용할 수 있어 더 편리할 것입니다.
오늘은 ts enum 사용 시 조심해야 할 점에 대해 알아보았습니다.
읽어주셔서 감사합니다.