[우아한타입리액트] 3장. 고급 타입(1)

Lina Hongbi Ko·2024년 11월 19일
0
post-thumbnail

3장. 고급 타입 (1)

💡 타입스크립트만의 독자적 타입 시스템

  • 타입스크립트는 자바스크립트 자료형에서 제시되지 않은 독자적인 타입 시스템을 가지고 있음

  • but 엄밀히 말하면 타입스크립트의 타입 시스템이 내포하고 있는 개념은 자바스크립트에서 기인한 것 (단지 자바스크립트로 표현할 수단과 필요성이 없었을 뿐)

  • 자바스크립트의 슈퍼셋으로 정적 타이핑을 할 수 있는 타입스크립트 등장 -> 타입스크립트의 타입 시스템 구축됨
    e.g) 타입스크립트의 any 타입을 살펴보면, 자바스크립트의 typeof 연산자나 Object.prototype.toString.call(...)를 사용해 콘솔에서 변수 타입을 추적해봐도 any 라는 문자열을 반환하는 경우를 찾을 수 없음 -> 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, 해당값을 컨트롤하려면 파악해야할 정보도 많음 ->도구의 도움을 받을 수 없는 상태에서 온전히 개발자 스스로 책임을 져야함 -> 실수할 가능성 커짐

📍 unknown 타입

  • any 타입과 유사하게 '모든 타입의 값'이 할당 될 수 있음

  • 그러나 'any를 제외한 다른 타입으로 선언된 변수'에는 unknown 타입 값을 할당할 수 없음

  • 비교

    • any
      • 어떤 타입이든 any 타입에 할당 가능
        • any 타입은 어떤 타입으로도 할당 가능 (단 never는 제외)
    • ukknown
      • 어떤 타입이든 unknown 타입에 할당 가능
        • unknown 타입은 any 타입 외에 다른 타입으로 할당 불가능
    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 타입

  • 앞서 함수 타입을 지정하는 방법에 대해 살펴보았는데, 함수에 전달되는 매개변수의 타입과 반환하는 타입을 지정해야하는 것을 알 수 있음

  • 이때 매개변수를 전달하지 않는 경우 -> 괄호를 비워두면 됨

  • 아무런 값을 반환하지 않는 경우 -> 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로 추론함

📍 never 타입

  • never 타입도 일반적으로 함수와 관련해 많이 사용되는 타입
  • never 라는 단어가 내포하고 있는 것처럼, 값을 반환할 수 없는 타입을 말함
  • 값을 반환하지 않는 것과 반환할 수 없는 것을 명확히 구분해야함
  • 자바스크립트에서 값을 반환할 수 없는 경우에는 크게 2가지로 나눌 수 있음
    • 에러를 던지는 경우
      : 자바스크립트에서는 런타임에 의도적으로 에러를 발생시키고 캐치할 수 있음. throw 키워드를 사용하면 에러를 발생시킬 수 있느네, 이는 값을 반환하는 것으로 간주 하지 않음.
      따라서, 특정 함수가 실행 중 마지막에 에러를 던지는 작업을 수행한다면 해당 함수의 반환 타입은 never.
    • 무한히 함수가 실행되는 경우
      : 함수 내에서 무한 루프를 실행하는 경우. 무한 루프는 결국 함수가 종료되지 않음을 의미하기 때문에 값을 반환하지 못함
  • never 타입은 모든 타입의 하위 타입
  • never 자신을 제외한 어떤 타입도 never 타입에 할당될 수 없다는 것을 의미 (any 타입이라 할지라도 never 타입에 할당될 수 없음)
  • 타입스크립트에서는 조건부 타입을 결정할 때, 특정 조건을 만족하지 않는 경우에 엄격한 타입 검사 목적으로 never 타입을 명시적으로 사용하기도 함

📍 Array 타입

*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 타입

  • 열거형이라고 부르는데 타입스크립트에서 지원하는 특수한 타입임

  • 일종의 구조체를 만드는 타입 시스템

  • 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

profile
프론트엔드개발자가 되고 싶어서 열심히 땅굴 파는 자

0개의 댓글