타입스크립트는 자바스크립트 자료형에서 제시되지 않은 독자적인 타입 시스템을 가지고 있음
but 엄밀히 말하면 타입스크립트의 타입 시스템이 내포하고 있는 개념은 자바스크립트에서 기인한 것 (단지 자바스크립트로 표현할 수단과 필요성이 없었을 뿐)
자바스크립트의 슈퍼셋으로 정적 타이핑을 할 수 있는 타입스크립트 등장 -> 타입스크립트의 타입 시스템 구축됨
e.g) 타입스크립트의 any 타입을 살펴보면, 자바스크립트의 typeof 연산자나 Object.prototype.toString.call(...)를 사용해 콘솔에서 변수 타입을 추적해봐도 any 라는 문자열을 반환하는 경우를 찾을 수 없음 -> any 타입은 타입스크립트에만 존재하는 독자적인 타입 시스템이라 생각할 수 있다.
하지만 any 타입의 개념은 이미 자바스크립트에서 널리 사용되고 있다. any는 어떤 타입이든 매핑할 수 있는 성질을 가지고 있는데 이것은 원래 자바스크립트의 사용 방식과 일치한다.
모든 타입 시스템은 타입스크립트에만 존재하는 키워드지만, 그 개념은 자바스크립트에 기인한 타입 시스템이다.
타입스크립트의 타입 계층 구조
자바스크립트에 존재하는 모든 값을 오류 없이 받을 수 있음
자바스크립트에서의 기본적인 사용방식과 같음 -> 타입을 명시하지 않은 것과 동일한 효과를 나타냄
any로 지정한 타입에 어떠한 값을 할당하더라도 오류가 발생하지 않음
let stat: any;
state = {value: 0}; // 객체를 할당해도
state = 100; // 숫자를 할당해도
state = "hello world"; // 문자열을 할당해도
state.foo.bar = () => console.log("this is any type");
// 중첩 구조로 들어가는 함수를 할당해도 문제없다
any 타입의 효용성에 대해 의문을 가질 수 있음 -> 타입스크립트를 쓰는 이유인 정적 타이핑을 무색하게 만들 수 있음
타입스크립트는 동적 타이핑 특징을 가진 자바스크립트에 정적 타이핑을 적용하는 것이 주된 목적이지만 any 타입은 이러한 목적을 무시하고 자바스크립트의 동적 타이핑으로 돌아가는 것과 비슷한 결과를 가져옴 -> any 타입을 변수에 할당하는 것은 지양해야 할 패턴으로 알려짐 => any를 회피하는 것은 좋은 습관
타입스크립트의 컴파일러 설정을 커스텀할 수 있는 tsconfig.json 파일에서 noImplicitAny 옵션을 활성화하면 타입이 명시되지 않은 변수의 암묵적인 any 타입에 대한 경고를 발생시킬 수 있음
but 타입스크립트에서 any 타입을 어쩔 수 없이 사용해야하 때가 있음
개발 단계에서 임시로 값을 지정해야할 때
: 매우 복잡한 구성 요소로 이루어진 개발 과정에서 추후 값이 변경될 가능성이 있거나 아직 세부 항목에 대한 타입이 확정되지 않은 경우가 생길 수 있다. 이때 해당 값을 any로 지정하면 경고 없이 개발을 할 수 있다. (타입을 세세하게 명시하는데 소요되는 시간 절약)
그러나 any 타입을 지나치게 남발하면 타입 안정성을 저해함.
any는 임시로 타입을 지정할 때 주로 사용되므로 타입에 대한 세부 스펙이 나오는 시점에 다른 타입으로 대체하는 경우가 많다. 이 때 any 타입으로 지정하고 나서 다른 타입으로 바꾸는 과정이 누락되면 문제가 발생할 수 있으므로 주의 해야한다.
어떤 값을 받아올지 또는 넘겨줄지 정할 수 없을 때
: API 요청 및 응답 처리, 콜백 함수 전달, 타입이 잘 정제되지 않아 파악이 힘든 외부 라이브러리 등을 사용할 때는 어떤 인자를 주고 받을지 특정하기 힘듦 -> 주고 받을 값이 명확하지 않을 때 열린 타입(any 타입)을 선언해야 할 수 있다.
type feedbackModalParams {
show: boolean;
content: string;
cancelButtonText?: string;
confirmButtonText?: stringn;
beforeOnClose?: () => void;
action?: any;
}
FeedbackMoalParams 라는 이름으로 선언된 타입 중, action 이라는 속성이 any로 선언된 것을 볼 수 있다. FeedbackMoalParams는 피드백을 나타내기 위해 모달 창을 그릴 때 사용되는 인자를 나타내는 타입이다. 이 중 action 속성은 모달 창을 그릴 때 실행될 함수를 의미한다. 모달 창을 화면에 그릴 때 다양한 범주의 액션에 따라 인자의 개수나 타입을 일일이 명시하기 힘들 수 있다. 이럴 때 any 타입을 사용하면 다양한 액션 함수를 전달 할 수 있다.
값을 예측할 수 없을 때 암묵적으로 사용
: 외부 라이브러리나 웹 API의 요청에 따라 다양한 값을 반환하는 API가 존재할 수 있다. 대표적인 예로 브라우저의 Fetch API를 들 수 있는데, Fetch API의 일부 메서드는 요청 이후의 응답을 특정 포맷으로 파싱하는데 이때 반환 타입이 any로 매핑되어 있는 것을 볼 수 있다.
async function load() {
const response = await fetch("https://api.com");
const data = await response.json(); // response.json()의 리턴타입은 Promise<any>로 정의되어 있다.
return data;
}
이렇게 예외적으로 any 타입을 사용해야 하는 상황이 있음에도 되도록 any타입은 지양하는 게 좋음 -> 타입 검사를 무색하게 만들고, 잠재적으로 위험한 상황을 초래할 가능성 커지기 때문
예시에서 개발자의 의도에 따르면 action 속성에 함수만 넘겨줘야 하지만, 실수 또는 함수가 아닌 값을 넘기더라도 타입스크립트는 이를 에러로 간주하지 않음 -> 타입스크립트의 컴파일러에서는 아무런 에러가 도출되지 않지만, 실제 런타임에서 심각한 오류 발생
any 타입은 개발자에게 편의성과 확장성을 제공 but, 해당값을 컨트롤하려면 파악해야할 정보도 많음 ->도구의 도움을 받을 수 없는 상태에서 온전히 개발자 스스로 책임을 져야함 -> 실수할 가능성 커짐
any 타입과 유사하게 '모든 타입의 값'이 할당 될 수 있음
그러나 'any를 제외한 다른 타입으로 선언된 변수'에는 unknown 타입 값을 할당할 수 없음
비교
let unknownValue: unknown;
unknownValue = 100; // any 타입과 유사하게 숫자이든
unknownValue = "hello world"; // 문자열이든
unknownValue = () => console.log("this is any type");
// 함수이든 상관없이 할당 가능하지만
let someValue1: any = unknownValue: // (O) any 타입으로 선언된 변수를 제외한 다른 변수는 모두 할당 불가
let someValue2: number = unknownValue; // (X)
let someValue3: string = unknownValue; // (X)
unknown 타입은 타입스크립트 3.0이 릴리스 될 때 추가되었음 -> 기존 타입 시스템에서 부족한 부분을 보완하기 위해 등장
unknown 타입은 이름처럼 무엇이 할당될지 아직 모르는 상태의 타입
예시를 보면, 함수를 unknwon 타입 변수에 할당할 때는 컴파일러가 아무런 경고를 주지 않지만 이를 실행하면 에러가 발생함
// 할당하는 시점에서는 에러가 발생하지 않음
const unknownFunction: unkown = () => console.log("this is any type");
// 하지만 실행 시에는 에러 발생 : Error: Object is of type 'unknown'.ts
unknownFunction();
함수 뿐만 아니라 객체의 속성 접근, 클래스 생성자 호출을 통한 인스턴스 생성 등 객체 내부에 접근하는 모든 시도에서 에러가 발생함
할당 시점에는 아무런 문제가 없는데 호출시에는 문제가 생김 -> unknown 타입은 어떤 타입이 할당되었는지 알 수 없음을 나타내기 때문에 unknown 타입으로 선언된 변수는 값을 가져오거나 내부 속성에 접근 할 수 없음
unknown 타입으로 할당된 변수는 어떤 값이든 올 수 있음을 의미하는 동시에 개발자에게 엄격한 타입 검사를 강제하는 의도를 담고 있음
any 타입을 사용하면 어떤 값이든 허용 -> 어떤 값이 할당될지 파악하기 어려운 상황에서 any 타입을 지정하여 임시로 문제를 회피하는 경우에 사용 됨
나중에 any 타입을 특정 타입으로 수정해야 하는 것을 깜빡하고 누락하면 어떤 값이든 전달 될 수 있어서 런타임에 예상치 못한 버그 발생할 가능성 높아짐
unknown 타입은 이러한 상황을 보완하기 위해 등장함 -> any 타입과 유사하지만 타입 검사를 강제하고, 타입이 식별된 후에 사용할 수 있기 때문에 any 타입보다 더 안전
데이터 구조를 파악하기 힘들 때 any 타입 대신 unknown 타입으로 대체해 사용하는 방법이 권장됨
any를 사용한다면 어떤 상황에서 사용할까요?
A: any는 웬만하면 쓰지 말자고 한다. 특히 런타임에 오류를 방지하고 싶다면 any를 쓰지 말아야한다. 하지만 특정 타입을 어떻게 좁혀서 사용할지 모를 때 어쩔 수 없이 anyㄹ르 사용한 적이 있다. 예를 들어 응답 객체의 구조를 정확히 알 수 없는 상황에서 응답 값이 너무 다양해 타입으로 정의하기 곤란한 상황이었는데 이때 any를 사용한 적이 있다. 하지만 지금 생각해보니 any를 쓸 필요가 없는 것 같아서 리팩토링하면서 전부 고쳐나가고 있다.
타입스크립트 베이스로 작성된 코드 공간 안에 데이터 플로우가 다 들어있다면 any가 필요한 상황을 떠올리기 힘들 것이다. 하지만 외부에서 어떤 값이 들어올지 모르는 상황이라면 any를 허용할 수 있다고 생각한다. axios 라이브러리의 응답 객체 기본값이 any인데 라이브러리에도 들어있는 것을 보면 any가 필요한 상황이 있을 것 같다.
그런데 깨진 유리창 이론처럼 any를 한 번 쓰기 시작하면 여기저기 남용하게 될까봐 걱정이 된다. 다른 함수나 객체에 최대한 영향을 주지 않도록 관리하는 것이 중요하다. 영향을 주기 시작하면 문제가 걷잡을 수 없이 많아질 수도 있을 것 같다.
B: 웬만하면 any를 지양하려 한다. 하지만 아예 안쓰기는 어려운 것 같다. 예를 들어 표 컴포넌트 같이 어떤 값을 받을지 모르는 상황에서 unknown을 사용하면, 가공할 때 타입 캐스팅을 모두 해야하는 상황이 생겨 이럴때는 any를 쓴다. 자바스크립트에서 타입스크립트로 변환하는 경우에도 그럴 수 있곘다.unknown은 어떨 때 사용할 수 있을까요?
A: 강제 타입 캐스팅을 통해 타입을 전환할 때 사용한다. const env = process.env as unknown as ProcessEnv 같은 식으로.
B: any 보다는 좀 더 많이 사용하는 것 같다. any는 무엇이든 괜찮다, unknown은 뭔지 모르지만 하나씩 테스트하면서 뭔지 알아내보자라는 의미라고 생각하는데 후자의 논리 전개가 필요할때 unknown을 사용한다. 또 unknown이 훨씬 안전한 것 같다. any로 선언된 변수가 있을 때 length 속성을 참조하면 에러가 나지 않는다. 그런데 unknown으로 선언된 것은 에러가 발생해서 더 안전하고 엄격하다고 생각한다.
C: 예상할 수 없는 데이터라면 unknown을 쓴다. 타입스크립트 4.4부터 try- catch 에러의 타입이 any에서 unknown으로 변경되어서 에러 핸들링 할 때도 unknown을 사용한다. 한편 as unkown as Type같이 강제 타입 캐스팅을 하기도 하는데 사실 이것도 any와 다를바 없어서 지양 해야 한다.
앞서 함수 타입을 지정하는 방법에 대해 살펴보았는데, 함수에 전달되는 매개변수의 타입과 반환하는 타입을 지정해야하는 것을 알 수 있음
이때 매개변수를 전달하지 않는 경우 -> 괄호를 비워두면 됨
아무런 값을 반환하지 않는 경우 -> void 지정
예를 들어, 콘솔에 로그를 출력하거나 다른 함수를 실행하는 역할만 하는 함수의 경우, 특정 값을 반환하지 않음
function showModal(type:ModalType): void {
feedbackSlice.actions.createModal(type);
}
// 화살표 함수 작성시
cosnt showModal = (type:ModalType): void => {
feedbackSlice.actions.createModal(type);
}
자바스크립트에서는 함수에서 명시적인 반환문을작성하지 않으면 기본적으로 undefined가 반환됨
하지만 타입스크립트에서는 void 타입이 사용되는데 이것은 undefined는 아님
타입스크립트에서 함수가 어떤 값을 반환하지 않는 경우, void를 지정해 사용한다고 생각할 것
void 타입은 변수에도 할당 할 수 있지만 함수가 아닌 값에 대해서는 대부분 무의미함
void 타입으로 지정된 변수는 undefined 또는 null 값만 할당할 수 있음
그런데 만약 tsconfig.json에서 strictNullChecks 옵션이 설정되었거나 컴파일 시 해당 플래그 설정이 실행되는 경우 -> null 값을 할당할 수 없음
let voidValue: void = undefined;
// strictNullChecks가 비활성화된 경우에 가능
voidValue = null;
명시적인 의미를 부여하는 관점에서 undefinedd와 null 타입 키워드를 직접 사용해서 타입을 지정하는 것이 더 바람직함
일반적으로 함수 자체를 다른 함수의 인자로 전달하는 경우가 아니라면 void 타입은 잘 명시하지 않는 경향이 있음 -> 함수 내부에 별도 반환문이 없다면 타입스크립트 컴파일러가 알아서 함수 타입을 void로 추론함
*Object.prototype.toString.call(...)연산자: 객체의 타입을 알아내는 데 사용하는 함수. typeof는 객체 타입을 단순히 object타입으로 알려주지만, Object.prototype.toString.call(...) 함수는 객체의 인스턴스까지 알려줌
Array는 배열 타입을 가리킴
cost arr = [];
console.log(typeof arr); // object
console.log(Object.prototype.toString.call(arr)); // [object Array]
자바스크립트에서는 배열을 객체에 속하는 타입으로 분류함(= 자바스크립트에서는 배열을 단독으로 배열이라는 자료형에 국한하지 않음)
타입스크립트에서는 Array라는 타입을 사용하기 위해서는 타입스크립의 특수한 문법을 함께 다뤄야함
자바스크립트의 배열은 동적 언어의 특징에 따라 어떤 값이든 배열의 원소로 허용(= 하나의 배열로 선언된 변수에 숫자, 문자열, 객체 등 자료형이 무엇이든 상관없이 원소를 삽입하고 관리가능)
const fn = () => console.log(1);
const array = [1, "string", fn]; // 자바스크립트에서는 배열에 숫자, 문자열, 함수 등 다양한 값을 삽입할 수 있음
array[0]; // 1
array[1]; // string
array[2]; // 1
이런 개념은 타입스크립트의 정적 타이핑과 잘 부합하지 않음
타입스크립트 뿐만 아니라 다른 정적 언어에서도 배열의 원소로 하나의 타입만 사용하도록 명시함
e.g) 자바, c++
// 자바
String[] array = {"string1", "string2", "string3"};
// String 타입의 배열을 선언된 array에 int, float 같은 다른 자료형의 원소는 허용하지 않는다.
// C++
int array[3] = {10, 20, 30};
// int 타입의 array는 다른 타입의 원소를 허용하지 않는다.
대개 정적 타입의 언어에서는 위와 같이 배열을 선언할 때 크기까지 동시에 제한하기도 함
타입스크립트에서는 일반적으로 배열의 크기까지 제한하진 않지만 정적 타입의 특성을 살려 명시적인 타입을 선언하여 해당 타입의 원소를 관리하는 것을 강제함 -> 자료형 + 대괄호([]) 형식을 사용해 배열 타입을 선언
const array: number[] = [1, 2, 3]; // 숫자에 해당하는 원소만 허용
자바스크립트에서 배열 타입을 Object.prototytpe.toString.call(...) 연산자로 확인해보면 Array가 반환됨
Array 키워드로 배열 타입을 선언하는 방법도 있음 -> 이를 위해선 제네릭이라는 특수한 문법 사용
const array: Array<number> = [1, 2, 3];
// number[]와 동일한 타입임
2가지 방식으로 배열 타입을 선어할 수 있음 -> 두 방식 간의 차이점은 선언하는 형식 외에는 없음 (개인의 선호나 팀의 컨벤션에 따라 하나의 방식으로 통일하거나 2가지 방식 혼용해서 사용해도 문제 없음)
기본적으로 자바스크립트의 동작은 배열 원소 타입을 구분하지 않기 때문에 다양한 자료형의 원소를 함께 다룰 수 있는데, 만약 숫자형과 문자열 등 여러 타입을 모두 관리해야하는 배열을 선언하려면 유니온 타입을 사용할 수 있음
const array1: Array<numbr | string> = [1, "string"];
const array2: number[] | string[] = [1, "string"];
// 후자의 방식은 아래와 같이 선언할 수도 있음
const array3: (number | string)[] = [1, "string"];
타입스크립트에서 배열 타입을 명시하는 것만으로 배열의 길이까지는 제한할 수 없음
그러나 튜플은 배열 타입의 하위 타입으로 기존 타입스크립트의 배열 기능에 길이 제한까지 추가한 타입시스템임
대괄호 안에 타입 시스템을 기술하는 것이 배열 타입과 다른 점
이때 대괄호 안에 선언하는 타입의 개수가 튜플이 가질 수 있는 원소의 개수를 나타냄
튜플은 배열의 특정 인덱스에 정해진 타입을 선언하는 것과 같음
let tuple: [number] = [1];
tuple = [1, 2]; // 불가능
tuple = [1, "string"];// 불가능
let tuple: [number, string, boolean] = [1, "string", true]; // 여러 타입과 혼합도 가능
기본적으로 타입스크립트에서의 배열과 튜플은 자바스크릡트와 달리 제한적으로 쓰임
배열은 사전에 허용하지 않은 타입이 서로 섞이는 것을 방지해 타입 안정성을 제공
튜플은 길이까지 제한해 원소 개수와 타입을 보장
타입을 제한하는 것은 자바스크립트가 갖는 동적 언어의 자유로움으로 인해 발생할 수 있는 런타임 에러와 유지보수의 어려움을 막기 위한 것
특히 튜플의 경우, 컨벤션을 잘 지키고 각 배열 원소의 명확한 의미와 쓰임을 보장할 때 더욱 안전하게 사용할 수 있는 타입임
튜플의 유용한 쓰임새를 알아보기 위해 사용자 인터페이스를 만들기 위한 자바스크립트 라이브러리인 리액트 예시를 보면, 리액트는 16.8 버전부터 도입된 훅이라는 요소 중 useState는 튜플 타입을 반환함
첫 번째 원소는 훅으로부터 생성 및 관리되는 상태 값을 의미하고, 두 번째 원소는 해당 상태를 조작할 수 있는 세터(setter)를 의미함
useState API는 배열 원소의 자리마다 명확한 의미를 부여하기 때문에 컴포넌트에서 사용하지 않은 값에 접근하는 오류를 방지 할 수 있음 & 구조 분해 할당을 사용해 사용자가 자유롭게 이름을 정의할 수 있음
import { useState } from 'react';
const [value, setValue] = useState(false);
const [username, setUsername] = useState("");
useState는 반환 값이 명확하고 잘 설계된 API이므로 튜플 타입을 통해 이처럼 유연성을 얻을 수 있음 (첫 번째 원소와 두 번째 원소의 타입과 의미가 명확하기 때문에 사용자는 그 의미에 맞게 적합한 이름을 선언하여 값을 가져올 수 있음)
구조 분해 할당은 배열 뿐만 아니라 객체에 대해서도 적용할 수 있음 -> 사전에 선언된 속성 이름을 통해 값을 가져오므로 튜플보다 유연성은 다소 떨어질 수 있음(순서에 의존하지 않고 값만 가져와야 하는 경우 객체 구조 분해 할당 방식이 더 유용할 수 있음)
const useStateWithObject = (initialValue: any) => {
...
return { value, setValue }
};
const { value, setValue } = useStateWithObject(false);
// 해당 함수에서 정의된 속성 이름으로 가져와야 한다
const { value: username, setValue: setUsername } = useStateWithObject('');
// 사용자 정의 이름으로 사용하고 싶다면 일차적으로 먼저 접근한 다음에 다른 이름으로 지정할 수 있다.
튜플과 배열의 성질을 혼합해서 사용할 수도 있음 -> 스프레드 연산자(...)를 사용하여 특정 인덱스에서 요소를 명확한 타입으로 선언하고 나머지 인덱스에서는 배열처럼 동일한 자료형의 원소를 개수 제한 없이 받도록 할 수 있음
const httpStatusFromPaths: [number, string, ...string[]] = [
400,
"Bad Request",
"/users/:id",
"/users/:userId",
"/users/:uuid",
];
// 첫 번째 자리는 숫자(400), 두 번째 자리는 문자열('Bad Request')을 받아야 하고, 그 이후로는 문자열 타입의 원소를 개수 제한 없이 받을 수 있음
옵셔널 프로퍼티(선택적 속성)를 명시하고 싶다면 물음표(?) 기호와 함께 해당 속성을 선언할 수 있음
해당 원소는 옵셔널 하기 때문에 해당 인덱스에 필수적으로 자리 잡고 있지 않을 수 있음을 의미
*옵셔널(optional) : 특정 속성 또는 매개변수가 값이 있을 수도 있고, 없을 수도 있는 것을 의미함. 즉, 선택적 매개변수(옵셔널 파라미터) 또는 선택적 속성(옵셔널 프로퍼티)은 필수적으로 존재하지 않아도 되며, 선택적으로 사용될 수 있음을 나타냄. 선택적 속성은 해당 속성에 값을 할당하지 않아도 되고, 해당 속성이 없어도 오류가 발생하지 않는다. 이는 타입스크립트에서 좀 더 유연한 데이터 모델링과 사용자 정의 타입을 지원하기 위한 개념이다.
const optionalTuple1: [number, number, number?] = [1, 2];
const optionaltuple2: [number, number, number?] = [1, 2, 3];
// 3번째 인덱스에 해당하는 숫자형 원소는 있어도 되고 없어도됨
열거형이라고 부르는데 타입스크립트에서 지원하는 특수한 타입임
일종의 구조체를 만드는 타입 시스템
enum을 사용해서 열거형을 정의할 수 있는데, 열거형은 각각의 멤버를 가지고 있음 (자바스크립트 객체의 모양새와 닮음)
타입스크립트는 명명한 각 멤버의 값을 스스로 추론함 -> 기본적인 추론 방식은 숫자 0부터 1씩 늘려가며 값을 할당하는 것
enum ProgrammingLanguage {
Typescript, // 0
Javascript, // 1
Java, // 2
Python, // 3
Kotlin, // 4
Rust, // 5
Go, // 6
}
// 각 멤버에게 접근하는 방식은 자바스크립트에서 객체의 속성에 접근하는 방식과 동일함
ProgrammingLanguage.Typescript; // 0
ProgrammingLanguage.Rust; // 5
ProgrammingLanguage["Go"]; // 6
// 또한 역방향으로도 접근 가능
ProgrammingLangauge[2]; // "Java"
각 멤버에 명시적으로 값 할당도 가능
모든 멤버에 일일이 값을 할당할 수도 있지만, 일부 멤버에게 값을 직접 할당하지 않아도 타입스크립트는 누락된 멤버를 이전 멤버 값의 숫자를 기준으로 1씩 늘려가며 자동으로 할당함
enum ProgrammingLanguage {
Typescript = "Typescript",
Javascript = "Javascript",
Java = 300,
Python = 400,
Kotlin, // 401
Rust, // 402
Go, // 403
}
주로 문자열 상수를 생성하는데 사용함 -> 응집력 있는 집합 구조체를 만들 수 있고, 사용자 입장에서도 간편하게 활용 가능(위 예시를 보면 ProgrammingLanguage 라는 이름의 열거형을 통해 각 멤버가 프로그래밍 언어와 관련된 값을 다룬다는 것을 알 수 있음)
열거형은 그 자체로 변수 타입으로 지정 할 수 있음 -> 이때 열거형을 타입으로 가지는 변수는 해당 열거형이 가지는 모든 멤버를 값으로 받을 수 있음
enum ItemStatusType {
DELIVERY_HOLD = "DELIVERY_HOLD", // 배송 보류
DELIVERY_READY = "DELIVERY_READY", // 배송 준비 중
DELIVERING = "DELIVERING", // 배송 중
DELIVERED = "DELIVERED" // 배송 완료
}
const checkItemAvailable = (itemStatus: ItemStatusType) => {
switch (itemStatus) {
case ItemStatusType.DELIVERY_HOLD:
case ItemStatusType.DELIVERY_READY:
case ItemStatusType.DELIVERING:
return false;
case ItemStatusType.DELIVERED:
default:
return true;
}
};
checkItemAvailable 함수의 인자인 itemStatus는 ItemStatusType 열거형을 타입으로 가짐 -> itemStatus의 타입이 문자열로 지정된 경우와 비교했을 때 다음과 같음
타입 안정성 : ItemStatusType에 명시되지 않은 다른 문자열은 인자로 받을 수 없음. 따라서 타입 안정성이 우수
명확한 의미 전달과 높은 응집력 : ItemStatusType에 타입이 다루는 값이 무엇인지 명확함. 아이템 상태에 대한 값을 모아놓은 것으로 응집력이 뛰어남
가독성 : 응집도가 높기 때문에 말하고자 하는 바가 더욱 명확함. 열거형 멤버를 통해 어떤 상태를 나타내는지 쉽게 이해할 수 있음 (eg. ItemStatusType.DELIVERY_HOLD vs DELIVERY_HOLD)
=> 열거형은 관련이 높은 멤버를 모아 문자열 상수처럼 사용하고자할 때 유용
but!(주의해야 할점 ->) 숫자로만 이루어져있거나 타입스크립트가 자동으로 추론한 열거형은 안전하지 않을 수 있음(역방향으로도 접근할 수 있어서, 할당된 값을 넘어서는 범위로 역방향으로 접근하더라도 타입스크립트는 막지 않음)
이러한 동작을 막기 위해 const enum으로 열거형을 선언하는 방법이 있음 -> 역방향으로의 접근을 허용하지 않음(자바스크립트의 객체에 접근하는 것과 유사한 동작 보장)
ProgrammingLanguage[200]; // undefined를 출력하지만 별다른 에러를 발생시키지 않음
// 아래처럼 선언하면 위와 같은 문제를 방지할 수 있음
cons enum ProgrammingLanguage {
// ...
{
그러나, const enum 으로 열거형을 선언해도 숫자 상수로 관리되는 열거형은 선언한 값 이외의 값을 할당하거나 접근하는 것을 방지하지 못함 <-> 반면 문자열 상수 방식으로 선언한 열거형은 미리 선언하지 않은 멤버로 접근하는 것을 방지함 => 문자열 상수 방식으로 열거형을 사용하는 것이 더 안전하며, 의도치 않은 값의 할당이나 접근 방지에 도움됨
const enum NUMBER {
ONE = 1,
TWO = 2
}
const myNumber: NUMBER = 100;
// NUMBER enum에서 100을 관리하고 있지 않지만 에러를 발생시키지 않음
const enum STRING_NUMBER {
ONE ="ONE",
TWO = "TWO"
}
const myStringNumber: STRING_NUMBER = "THREE" // Error
열거형의 가장 큰 문제는 타입 공간과 값 공간에서 모두 사용되는데, 열거형은 타입스크립트 코드가 자바스크립트로 변환 될 때 즉시 실행 함수 형식으로 변환되는 것을 볼 수 있음
이때, 번들러에서 트리쉐이킹 과정 중 즉시 실행 함수로 변환된 값을 사용하지 않는 코드로 인식하지 못하는 경우가 발생 할 수 있음 -> 불필요한 코드의 크기 증가
이럴 때는 const enum 또는 as const assertion 사용해서 유니온 타입으로 열거형과 동일한 효과를 얻는 방법이 있음
*이 부분 읽으면 도움되는 글 : https://xpectation.tistory.com/218#const%20enum-1