Part5_TypeUtilize
이효경
책 읽기

5장 타입 활용하기

5.1 조건부 타입

  • 조건에 따라 다른 타입을 반환해야 될때가 있다. 타입스크립트에서는 조건부 타입을 사용해 조건에 따라 출력 타입을 다르게 도출할 수 있다.
  • 조건부 타입을 활용하면 중복되는 타입 코드를 제거하고 상황에 따라 적절한 타입을 얻을 수 있기 때문에 더욱 정확한 타입 추론을 할 수 있게 된다.

5.1.1 extends와 제네릭을 활용한 조건부 타입

  • extends 키워드는 타입스크립트에서 다양한 상황에서 활용된다. 타입을 확장할 때와 타입을 조건부로 설정할 때 사용되며, 제네릭 타입에서는 한정자 역할로 사용된다.
T extends U ? X : Y
  • 위와 같이 조건부 타입에서 extends를 사용할 때는 자바스크립트 삼항 연산자와 함께 쓴다.
type PayMethod<T> = T extends 'card' ? Card : Bank;
  • 위 코드의 PayMethod 타입은 제네릭 타입으로 extends를 사용한 조건부 타입이다. 제네릭 매개변수에 "card"가 들어오면 Card 타입, 그 외의 값이 들어오면 Account 타입으로 결정된다.

5.1.2. 조건부 타입을 사용하지 않았을 때의 문제점

  • 타입 설정이 유니온으로만 되어 있을 경우 타입 스크립트는 해당 타입에 맞는 Data 타입을 추론할 수 없다.
  • 인자에 따라 반환되는 타입을 설정하고 싶다면 extends을 사용한 조건부 타입을 활용하면 된다.

5.1.3. extends 조건부 타입을 활용하여 개선하기

  • 제네릭과 extends를 함께 사용해 제네릭으로 받는 타입을 제한했다. 따라서 개발자는 잘못된 값을 넘길 수 없기 때문에 휴먼 에러를 방지할 수 있다.
  • extends를 활용해 조건부 타입을 설정했다. 조건부 타입을 사용해서 반환 값을 사용자가 원하는 값으로 구체화할 수 있다. 이에 따라 불필요한 타입 가드, 타입 단언 드을 방지할 수 있다.

5.1.4 infer를 활용해서 타입 추론하기

  • extends를 사용할 때 infer 키워드를 사용할 수 있다. infer는 '추론하다'라는 의미를 지니고 있는데 타입스크립트에서도 타입을 추론하는 역할을 한다.
type UnpackPromise<T> = T extends Promise<infer K>[] ? K : any;
  • UnpackPromise 타입은 제네릭으로 T를 받아 T가 Promise로 래핑된 경우라면 K를 반환하고 그렇지 않은 경우에는 any를 반환한다. Promise<infer K>는 Promise의 반환 값을 추론해 해당 값의 타입을 K로 한다는 의미이다.

5.2 템플릿 리터럴 타입 활용하기

  • 타입스크립트에서는 유니온 타입을 사용하여 변수 타입을 특정 문자열로 지정할 수 있다.
  • 이 기능을 사용하면 컴파일 타임의 변수에 할당되는 타입을 특정 문자열로 정확하게 검사하여 휴면 에러를 방지할 수 있고, 자동 완성 기능을 통해 개발 생산성을 높일 수 있다.
type HeaderTag = 'h1' | 'h2' | 'h3' | 'h4' | 'h5';
 
type HeadingNumber = 1 | 2 | 3 | 4 | 5;
type HeaderTag = `h${HeadingNumber}`;
  • 주의할 점 : 타입스크립트 컴파일러가 유니온을 추론하는데 시간이 오래 걸리면 비효율적이기 때문에 타입스크립트가 타입을 추론하지 않고 에러를 내뱉을 때가 있다. 따라서 템플릿 리터럴 타입에 삽입된 유니온 조합의 경우 너무 많지 않게 적절하게 나누어 타입을 정의하는 것이 좋다.

5.3 커스텀 유틸리티 타입 활용하기

  • 타입스크립트에서 제공하는 유틸리티 타입만으로는 표현하기 어려울 때 커스텀 유틸리티 타입을 제작해서 사용한다.

5.3.1. 유틸리티 함수를 활용해 styled-components의 중복 타입 선언 피하기

  • styled-components로 만든 컴포넌트에 넘겨주는 타입은 props에서 받은 타입과 동일할 때가 대부분이다. 이러한 경우에는 타입스크립트에서 제공하는 Pick, Omit과 같은 유틸리티 타입을 잘 활용하여 코드를 간결하게 작성할 수 있다.
type StyledProps = Pick<Props, 'height' | 'color' | 'isFull'>;
  • 위처럼 Pick 유틸리 타입을 활용하여 props의 필요한 부분만 선택하여 styled-components 컴포넌트의 타입을 정의하면, 중복된 코드를 작성하지 않아도 되고 유지보수를 더욱 편리하게 해줄 수 있다.

5.3.2. PickOne 유틸리티 함수

  • 생략

5.4 불변 객체 타입으로 활용하기

  • 키 타입을 해당 객체에 존재하는 키값으로 설정하는 것이 아니라 string으로 설정하면 getColorHex함수의 반환 값은 any가 된다. colors에 어떤 값이 추가될지 모르기 때문이다.
const colors = {
    red: '#F2342',
    green: '#0CWEF',
    blue: '1ADF3'
};
 
const getColorHex = (key: string) => colors[key];
  • 여기서 as const 키워드로 객체를 불변 객체로 선언하고, keyof 연산자를 사용하여 getColorHex 함수 인자로 실제 colors 객체 존재하는 키값만 받도록 설정할 수 있다. keyof, as const로 객체 타입을 구체적으로 설정하면 타입에 맞지 않는 값을 전달할 경우 타입 에러가 반환되기 때문에 컴파일 단계에서 발생할 수 있는 실수를 방지할 수 있다. 또한 자동 완성 기능을 통해 객체에 어떤 값이 있는지 쉽게 파악할 수 있게 된다. 즉, 이러한 방법으로 객체 타입을 더 정확하고 안전하게 설정할 수 있다.

5.5 Record 원시 타입 키 개선하기

  • 객체 선언 시 키가 어떤 값인지 명확하지 않다면 Record의 키를 string이나 number 같은 원시타입으로 명시하곤 했다. 이때 타입스크립트는 키가 유효하지 않더라도 타입상으로는 문제없기 때문에 오류를 표시하지 않는다. 이것은 예상치못한 런타임 에러를 발생시킬 수 있다.

5.5.1. 무한한 키를 집합으로 가지는 Record

type Category = string;
interface Food {
    name: string;
    //...
}
 
const foodByCategory: Record<Category, Food[]> = {
    한식: [{ name: '제육덮밥' }, { name: '뚝배기 불고기' }],
    일식: [{ name: '초밥' }, { name: '텐동' }]
};
 
foodByCategory['양식']; // Food[]로 추론
foodByCategory['양식'].map(food => console.log(food.name)); //오류가 발생하지 않음
  • 위 코드는 음식 분류를 키로 사용하는 음식 배열이 담긴 객체이다. Category의 타입은 string이다. Category를 Record의 키로 사용하는 foodBy-Category 객체는 무한한 키 집합을 가지게 된다. 이때 foodByCategory 객체에 없는 키값을 사용하더라도 타입스크립트는 오류를 표시하지 않는다.
  • 그러나 foodByCategory["양식"]은 런타임으로 undefined가 되어 오류를 반환한다.
foodByCategory['양식']?.map(food => console.log(food.name)); //오류가 발생하지 않음
  • 위처럼 옵셔널 체이닝을 쓰면 런타임 에러를 방지할 수 있다.
  • 그러나 어떤 값이 undefined인지 매번 판단해야 한다.

5.5.2. 유닛 타입으로 변경하기

  • 키가 유한한 집합이라면 유닛 타입을 사용할 수 있다.
type Category = '한식' | '일식';
 
interface Food {
    name: string;
    //...
}
 
const foodByCategory: Record<Category, Food[]> = {
    한식: [{ name: '제육덮밥' }, { name: '뚝배기 불고기' }],
    일식: [{ name: '초밥' }, { name: '텐동' }]
};
 
foodByCategory['양식']; // Food[]로 추론
foodByCategory['양식'].map(food => console.log(food.name)); //오류가 발생하지 않음
  • 위처럼 Catagory에는 한식 또는 일식만 올 수 있기때문에 양식을 키로 사용하면 에러가 발생한다. 이처럼 유닛 타입을 활용하면 개발 중에 유효하지 않은 키가 사용되었는지 확인할 수 있다.
  • 하지만 키가 무한한 상황에는 적합하지 않다.

5.5.3. Partial을 활용하여 정확한 타입 표현하기

  • 키가 무한한 상황에서 Partial을 사용하여 해당 값이 undefined일 수 있는 상태임을 표현할 수 있다. 객체 값이 undefined일 수 있는 경우에 Partial을 사용하여 PartialRecord 타입을 선언하고 객체를 선언할 때 이것을 활용할 수 있다.