728x90

1. 타입 확장하기

타입 확장은 기존 타입을 사용해서 새로운 타입을 정의하는 것이다. 

타입 확장의 장점은 1. 확장성 2. 코드 중복 방지이다

 

  1. 유니온 타입
    2개 이상의 타입을 조합하여 사용하는 방법 (합집합의 개념)
  2. 교차 타입
    기존 타입을 합쳐 필요한 모든 기능을 가진 하나의 타입(교집합의 개념)
    type IdType = string | number;
    type Numeric = number | boolean;
    type Universal = IdType & Numeric; // number​
  3. extends
    유니온과 교차 타입을 사용하여 새 타입을 만드는 것은 type 키워드에서만 가능. 
    interface에서는 extends로 교차 타입을 작성한다.
    interface BaseMenuItem {
      itemName: string | null;
      itemImageUrl: string | null;
    }
    
    interface BaseCartItem extends BaseMenuItem {
      quantity: number;
    }​

    하지만 차이점은 있다.
    interface DelTip {
      tip: number;
    }
    interface Filter extends DelTip {
      tip: string;
      //'Filter' 인터페이스가 'DelTip' 인터페이스를 잘못 확장합니다.
      //'tip' 속성의 형식이 호환되지 않습니다.
    }
    
    type DelTip2 = { tip: number };
    type Fliter2 = DelTip2 & { tip: string }; // never타입​
     extends에서는 중첩되는 키값의 타입이 다른 경우 에러가 뜨지만, 교차타입에서는 never타입이 된다.

확장을 통해서 새로운 타입을 만들 경우, 네이밍의 의도를 명확히 표현할 수도 있고, 작성 단계에서 예상치 못한 에러도 예방할 수 있다.

 

2. 타입 좁히기(타입 가드)

타입 좁히기는 변수 또는 표현식의 타입 범위를 더 작은 범위로 좁혀나가는 과정을 말한다.

자바스크립트 연산자를 활용한 타입가드는 typeof, instanceof ,in 같은 연산자를 활용하여 제어문으로 특정 타입 값을 가질 수 밖에 없는 상황을 유도하여 좁히는 방법이 있다.

  1. 원시 타입을 추론할 떄 : typeof 연산자 활용
    string, number, boolean, undefined, object, function, bigint, symbol의 경우 typeof를 통해 타입을 좁힐 수 있다.
    const replaceHyphen: (date: string | Date) => string | Date = (date) => {
      if (typeof date === 'string') {
      //이 분기에서는 date 타입이 string이다.
        return date.replace(/-/g, '/');
      }
      return date;
    };​
  2. 인스턴스화된 객체 타입을 판별할 때: instanceof 연산자 활용
    인스턴스화된 객체 타입을 판별하는 타입 가드로 쓸 수 있다.
    const onKeyDown = (event: KeyboardEvent) => {
      if (event.target instanceof HTMLInputElement && event.key === 'Enter') {
        event.target.blur();
      }
    };​
  3. 객체의 속성이 있는지 없는지에 따른 구분  : in 연산자 활용
    in 연산자는 객체의 속성이 있는지 확인한 다음 true/false를 반환한다. 이를 통해 속성 여부에 따른 객체 타입 구분을 할 수 있다.
    type Fish = { swim: () => void };
    type Bird = { fly: () => void };
    
    function move(animal: Fish | Bird) {
      if ('swim' in animal) {
        return animal.swim();
      }
    
      return animal.fly();
    }​


  4. is 연산자로 사용자 정의 타입 가드 만들어 활용하기 (타입 명제 활용)
    반환 타입이 타입 명제인 함수를 정의하여 사용하는 방식
    type Fish = { swim: () => void };
    type Bird = { fly: () => void };
    
    //is
    function isFish(pet: Fish | Bird): pet is Fish {
      return (pet as Fish).swim !== undefined;
    }
    function getSmallPet(): Fish | Bird {
      const randomNumber = Math.random();
    
      if (randomNumber < 0.5) {
        return { swim: () => console.log('Fish is swimming') };
      } else {
        return { fly: () => console.log('Bird is flying') };
      }
    }
    
    let pet = getSmallPet();
    
    if (isFish(pet)) {
      pet.swim();
    } else {
      pet.fly();
    }​

3. 타입 좁히기(식별할 수 있는 유니온)

태그된 유니온 혹은 식별할 수 있는 유니온으로도 타입을 좁힐 수 있다.

아래에는 자바스크릡트가 덕 타이핑 언어이기 때문에 별도의 타입 에러를 발생하지 않는 wrongErrorArr이 있다.

존재하지 않는 타입이지만 에러가 생기지 않았고, 이럴 경우 무수한 에러 객체가 생겨날 위험이 생긴다.

type TextError = {
  errorCode: string;
  errorMessage: string;
};

type ToastError = {
  errorCode: string;
  errorMessage: string;
  toastShowDuration: number; //토스트 시간
};

type AlertError = {
  errorCode: string;
  errorMessage: string;
  onConfirm: () => void; // 확인 버튼 후 액션
};

type ErrorFeedbackType = TextError | ToastError | AlertError;
const errorArr: ErrorFeedbackType[] = [
  { errorCode: '100', errorMessage: '텍스트' },
  { errorCode: '200', errorMessage: '토스트', toastShowDuration: 3000 },
  { errorCode: '300', errorMessage: '앨럿', onConfirm: () => {} },
];

const wrongErrorArr: ErrorFeedbackType[] = [
  {
    errorCode: '900',
    errorMessage: '잘못된 에러',
    toastShowDuration: 3000,
    onConfirm: () => {},
  },
];

 

 

이를 해결하기 위해서 에러 타입을 구분할 필요가 있는데, 비슷한 구조를 가지지만 서로 호완되지 않도록 만들어주기 위해서는 타입들이 서로 포함 관계를 가지지 않도록 정의해야 한다. 이때 사용하는 것이 식별할 수 있는 유니온 이다.

 

식별할 수 있는 유니온 
타입간의 구조 호환을 막기 위해 타입마다 구분할 수 있는 판별자를 달아 주어 포함 관계를 제거하는 것

type TextError2 = {
  errorType: 'Text';
  errorCode: string;
  errorMessage: string;
};

type ToastError2 = {
  errorType: 'Toast';
  errorCode: string;
  errorMessage: string;
  toastShowDuration: number; //토스트 시간
};

type AlertError2 = {
  errorType: 'Alert';
  errorCode: string;
  errorMessage: string;
  onConfirm: () => void; // 확인 버튼 후 액션
};

type ErrorFeedbackType2 = TextError2 | ToastError2 | AlertError2;
const errorArr2: ErrorFeedbackType2[] = [
  { errorType: 'Text', errorCode: '100', errorMessage: '텍스트' },
  {
    errorType: 'Toast',
    errorCode: '200',
    errorMessage: '토스트',
    toastShowDuration: 3000,
  },
  {
    errorType: 'Alert',
    errorCode: '300',
    errorMessage: '앨럿',
    onConfirm: () => {},
  },
];

const wrongErrorArr2: ErrorFeedbackType2[] = [
  {
    errorType: 'Toast',
    errorCode: '900',
    errorMessage: '잘못된 에러',
    toastShowDuration: 3000,
    // onConfirm: () => {}, //개체 리터럴은 알려진 속성만 지정할 수 있으며 'ToastError2' 형식에 'onConfirm'이(가) 없습니다
  },
];

 

errorType을 통해서 구별할 수 있게 된다.

 

이때 식별할 수 있는 유니온을 선정할 때는 판별자는 유닛 타입으로 선언되어야 정상적으로 작동한다.

유닛 타입 : 오직 하나의 정확한 값을 가지는 타입 (null, undefined, 리터럴 타입,1 등등)

 

공식 깃허브에서는 2가지 조건을 건다

  • 리터럴 타입
  • 판별자로 선정한 값ㅇ에 적어도 하나 이상의 유닛 타입이 포함되어야 하며, 인스턴스화 할 수 있는 타입은 포함되면 안된다.

4. Exhaustiveness Checking 으로 정확한 타입 분기 유지하기

모든 케이스에 대해서 철저하게 타입을 검사하는 것을 말하며 타입 좁히기에 사용되는 패러다임 중 하나

Exhuastiveness Checking을 통해 모든 케이스에 대한 타입 검사를 강제할 수 있다.

const getProduction = (productPrice: ProductPrice): string => {
  if (productPrice === '10000') return '배민상품권 1만원';
  if (productPrice === '20000') return '배민상품권 2만원';
  else exhaustiveCheck(productPrice); //'string' 형식의 인수는 'never' 형식의 매개 변수에 할당될 수 없습니다.ts(2345)
  return '배민상품권';
};

const getProduction2 = (productPrice: ProductPrice): string => {
  if (productPrice === '10000') return '배민상품권 1만원';
  if (productPrice === '20000') return '배민상품권 2만원';
  if (productPrice === '50000') return '배민상품권 5만원';
  else {
    exhaustiveCheck(productPrice);
    return '배민상품권';
  }
};


const exhaustiveCheck = (param: never) => {
  throw new Error('type Error!');
};

 

 

이 부분에 대해서는 exhaustiveCheck 함수가  런타임 코드에 포함되기 때문에 의견이 분분하지만, 우형 이야기에서는 성능에 거의 영향도 없고, 테스트 코드로 해결할 수 있는 부분도 있지만 소통 측면에서 매력적이다고 하였습니다.

 

 

var getProduction2 = function (productPrice) {
    if (productPrice === '10000')
        return '배민상품권 1만원';
    if (productPrice === '20000')
        return '배민상품권 2만원';
    if (productPrice === '50000')
        return '배민상품권 5만원';
    else {
        exhaustiveCheck(productPrice);
        return '배민상품권';
    }
};
var exhaustiveCheck = function (param) {
    throw new Error('type Error!');
};

JS파일에도 exhaustiveCheck 함수가 남아 있다.

+ Recent posts