#14 타입 연산과 제너릭 사용으로 반복 줄이기

2023. 1. 9.·🎨 프론트엔드 공부/JS & TS

이펙티브 타입스크립트 (댄 밴더캄 지음) 를 읽고 정리

📍요약

✅같은 코드를 반복하지 말라 (DRY : Don't repeat yourself) 원칙을 타입에도 적용해야 한다

✅타입 인덱싱, extends를 통해 인터페이스 필드의 반복 줄일 수 있음

✅추가로 keyof, typeof, 매핑된 타입을 활용

✅제네릭 타입은 타입을 위한 함수이며 제네릭 매개변수를 제한하기 위해 extends 활용 가능

✅Pick, Partial, ReturnType 같은 유틸리티 타입에 익숙해야 함

 

📍코드 반복 줄이기 1

사용한 개념

1️⃣타입 인덱싱

2️⃣매핑된 타입

3️⃣Pick 유틸리티 타입

 

interface State {
    userId: string;
    pageTitle: string;
    recentFiles: string[];
    pageContents: string;
}

interface TopNavState {
    userId: string;
    pageTitle: string;
    recentFiles: string[];
}

// TopNavState 를 확장하여 State를 구성하는 것보다
// State의 부분 집합으로 TopNavState를 정의하는 것이 바람직하다

// 1. State 타입의 프로퍼티를 인덱싱하여 참조할 수 있다
type TopNavState1 = {
    userId: State["userId"];
    pageTitle: State["pageTitle"];
    recentFiles: State["recentFiles"];
}

// 2. 매핑된 타입을 사용하면 중복을 더 줄일 수 있다
type TopNavState2 = {
    [k in "userId" | "pageTitle" | "recentFiles"]: State[k]
}

// 3. 매핑된 타입은 Pick 유틸리티 타입과 동일하다
type TopNavState3 = Pick<State, 'userId' | 'pageTitle' | 'recentFiles'>;
// Pick<T, K extends keyof T> = { [P in K]: T[P]; }

 

- enum은 tree-shaking이 안되서 union을 더 많이 사용한다

 

📍유니온 타입 vs Pick

interface SaveAction {
  type: 'save';
  // ...
}
interface LoadAction {
  type: 'load';
  // ...
}
type Action = SaveAction | LoadAction;
type ActionType = "save" | "load"; // 타입 반복

// 타입 인덱싱하여 반복 줄이기
type ActionType1 = Action['type'];  // Type is "save" | "load"

// Pick을 사용하면 type 프로퍼티를 갖는 interface가 만들어진다
type ActionRec = Pick<Action, "type">; // {type: "save" | "load"}

 

📍코드 반복 줄이기 2

사용한 개념

1️⃣매핑된 타입

2️⃣keyof 연산자

3️⃣Partial 유틸리티 타입

interface Options {
  width: number;
  height: number;
  color: string;
  label: string;
}
interface OptionsUpdate {
  width?: number;
  height?: number;
  color?: string;
  label?: string;
}
class UIWidget {
  constructor(init: Options) { /* ... */ }
  update(options: OptionsUpdate) { /* ... */ }
}

// 매핑된 타입, keyof, ? 를 사용하여 Options로부터 OptionsUpdate를 만들 수 있다
type OptionsUpdate2 = { [k in keyof Options]?: Options[k] }

// keyof는 타입을 받아서 속성 타입의 유니온을 반환
type OptionsKeys = keyof Options; // Type is "width" | "height" | "color" | "label"

 

- 매핑된 타입, keyof, ? 패턴은 Partial 유틸리티 타입과 동일하다

위에서 만들어본 OptionsUpdate 를 Partial로 대체할 수 있다

interface Options {
  width: number;
  height: number;
  color: string;
  label: string;
}
class UIWidget {
  constructor(init: Options) { /* ... */ }
  update(options: Partial<Options>) { /* ... */ }
}

 

📍코드 반복 줄이기 3

사용한 개념

1️⃣typeof 연산자

const INIT_OPTIONS = {
  width: 640,
  height: 480,
  color: '#00FF00',
  label: 'VGA',
};
interface Options {
  width: number;
  height: number;
  color: string;
  label: string;
}

// typeof 연산자를 통해 INIT_OPTIONS 객체의 필드 값의 타입을 추출
type Options2 = typeof INIT_OPTIONS;

 

📍ReturnType 유틸리티 타입

- ReturnType 유틸리티 타입을 통해 (함수의) 리턴 타입을 추출할 수 있다

const INIT_OPTIONS = {
  width: 640,
  height: 480,
  color: '#00FF00',
  label: 'VGA',
};
function getUserInfo(userId: string) {
  // COMPRESS
  const name = 'Bob';
  const age = 12;
  const height = 48;
  const weight = 70;
  const favoriteColor = 'blue';
  // END
  return {
    userId,
    name,
    age,
    height,
    weight,
    favoriteColor,
  };
}
// Return type inferred as { userId: string; name: string; age: number, ... }

// ReturnType 유틸리티 타입을 통해 리턴 타입을 추출할 수 있다
type UserInfo = ReturnType<typeof getUserInfo>

 

📍제너릭 매개변수

아래 코드에서는 제너릭 매개변수 대신 객체 리터럴 타입을 사용했다

{first: string} 이 Name을 extends 하지 않아서 에러가 발생한다

 

interface Name {
  first: string;
  last: string;
}
type DancingDuo<T extends Name> = [T, T]; // Name이 T의 부분집합

const couple1: DancingDuo<Name> = [
  {first: 'Fred', last: 'Astaire'},
  {first: 'Ginger', last: 'Rogers'}
];  // OK
const couple2: DancingDuo<{first: string}> = [
                       // last 프로퍼티가 누락되어 에러 발생
                       
  {first: 'Sonny'},
  {first: 'Cher'}
];

type FirstLast = Pick<Name, 'first' | 'last'>;  // OK
type FirstMiddle = Pick<Name, 'first' | 'middle'>; // Name에 없는 키는 쓸 수 없음

 

Pick의 정의 살펴보기

type Pick<T, K> = {
  [k in K]: T[k] // 에러 : K 타입은 string | number | symbol 타입에 할당할 수 없다
  // 즉, K 가 객체의 프로퍼티가 될 수 있는지 모르기 때문에 에러 발생
}

// K는 T의 부분집합이 되면 에러 해결
type Pick<T, K extends keyof T> = {
  [k in K]: T[k] // 정상
}

 

'🎨 프론트엔드 공부/JS & TS' 카테고리의 다른 글
  • #16 number 인덱스 시그니처 보다는 Array, 튜플, ArrayLike를 사용하기
  • #15 동적 데이터에 인덱스 시그니처 사용하기
  • #13 타입과 인터페이스의 차이점 알기
  • #12 함수 표현식에 타입 적용하기
지식물원
지식물원
지식이 자라는 식물원!
  • 지식물원
    지식물원
    지식물원
  • 전체
    오늘
    어제
    • 분류 전체보기 (510)
      • 🎨 프론트엔드 공부 (247)
        • JS & TS (86)
        • HTML & CSS (22)
        • React & Next (49)
        • Vue & Nuxt (22)
        • 기타 (68)
      • 🤓 기술 학습 & 공부 기록 (116)
        • Node.js (0)
        • Python (37)
        • 백엔드 (0)
        • 딥러닝 (1)
        • 컴퓨터 일반 (72)
        • 개발 인프라 (6)
      • 👨‍💻 프로젝트 경험 (6)
        • Work (0)
        • Toy (6)
      • ⚙️ 개발 팁 & 노하우 (21)
        • 프론트엔드 (6)
        • 기타 (15)
      • ☕️ 커리어 & 인터뷰 준비 (88)
        • 코딩 테스트 (88)
      • 📰 기술 트렌드 & 생각 정리 (4)
      • 📚 기타 (25)
        • 마케팅 (15)
        • 비개발서적 (10)
  • 블로그 메뉴

    • 태그
  • 링크

  • 공지사항

    • 모바일 접속 시 코드 하이라이팅 깨질 때
  • 인기 글

  • hELLO· Designed By정상우.v4.10.3
지식물원
#14 타입 연산과 제너릭 사용으로 반복 줄이기
상단으로

티스토리툴바