[Effective TypeScript] 2장 14 타입 스크립트의 타입시스템

채동기·2023년 2월 17일
0

TypeScript

목록 보기
6/21

아이템 14) 타입연산과 제네릭 사용으로 반복 줄이기

중복되는 부분에 타입을 붙여 반복을 줄일 수 있습니다.

function distance(a: { x: number; y: number }, b: { x: number; y: number }) {
  return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}
interface Point2D {
  x: number;
  y: number;
}
function distance(a: Point2D, b: Point2D) {
  return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}

아이템 12에 나왔던 시그니처를 명명된 타입으로 분리해낼 수 있습니다.

function get(url: string, opts: Options): Promise<Response> { /*...*/ }
function post(url: string, opts: Options): Promise<Response> { /*...*/ }
type HTTPFunction = (url: string, options: Options) => Promise<Response>;
const get: HTTPFunction = (url, options) => {};
const post: HTTPFunction = (url, options) =>{}

인터페이스 확장을 통해서 반복을 제거할 수 있습니다.

interface Person {
  firstName: string;
  lastName: string;
}

interface PersonWithBirthDate extends Person {
  birth: Date;
}

일반적인 방벅은 아니지만 다음처럼 할 수있습니다.
유니온 타입에 속성을 추가할 때 특히 유용합니다.

type PersonWithBirthDate = Person & { birth: Date };
 interface State {
    userId: string;
    pageTitle: string;
    recentFiles: string[];
    pageContents: string;
  }
  interface TopNavState {
    userId: string;
    pageTitle: string;
    recentFiles: string[];
  }

위의 예시를 state를 인덱싱하여 중복을 제거할 수 있습니다.

type TopNavState = {
    userId: State['userId'];
    pageTitle: State['pageTitle'];
    recentFiles: State['recentFiles'];
  };

그러나 여전히 반복되는 코드가 존재합니다.
이 때 '매핑된 코드'를 사용하면 좀 더 나아집니다.

type TopNavState = {
    [k in 'userId' | 'pageTitle' | 'recentFiles']: State[k]
  };

매핑된 타입은 배열의 필드를 루프 도는 것과 같은 방식입니다. 이 패턴은 표준 라이브러리에서도 일반적으로 찾을 수 있으며, Pick이라고 합니다.

type Pick<T,K> = { [k in K]: T[k] };
 type TopNavState = Pick<State, 'userId' | 'pageTitle' | 'recentFiles'>;

유니온에서도 다른 형태의 중복이 발생할 수 있습니다.

  interface SaveAction {
    type: 'save';
    // ...
  }
  interface LoadAction {
    type: 'load';
    // ...
  }
  type Action = SaveAction | LoadAction;
  type ActionType = 'save' | 'load';  

Action 유니온을 인덱싱해서 중복을 줄일수 있습니다.

type ActionType = Action['type'];  

Pick을 사용하여 얻게되는, type 속성을 가지는 인터페이스와는 다릅니다.

type ActionRec = Pick<Action, 'type'>;  // {type: "save" | "load"}

업데이트가 되는 클래스를 정의하면 많은 중복이 발생합니다.

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를 사용하면 update 타입을 만들 수 있습니다.

type OptionsUpdate = {[k in keyof Options]?: Options[k]};

keyof 는 속성 타입의 유니온을 반환합니다.

type OptionsKeys = keyof Options;
// "width" | "height" | "color" | "label"
  • Partial
    매핑된 타입 (k in keyof Options)은 순회하며 Options 내 k 값에 해당하는 속성이 있는지 찾습니다. ?는 각 속성을 선택적으로 만듭니다. 이 패턴 역시 아주 일반적이면 표준 라이브러리에 partial이라는 이름으로 포함되어 있습니다.

함수나 메서드의 반환 값에 명명된 타입을 만들고 싶을 수도 있습니다.

function getUserInfo(userId: string) {
  const name = "Bob";
  const age = 12;
  const height = 48;
  const weight = 70;
  const favoriteColor = "blue";
  return {
    userId,
    name,
    age,
    height,
    weight,
    favoriteColor,
  };
}
// 반환된 타입은 { userId: string; name: string; age: number, ... }

이런 경우 ReturnType제네릭이 정확히 들어맞습니다.

type UserInfo = ReturnType<typeof getUserInfo>;

제네릭 타입은 타입을 위한 함수와 같습니다. 타입을 반복하는 대신 제네릭 타입을 사용하여 타입들 간에 매핑하는 것이 좋습니다. 제네릭 타입에 익숙해져야합니다. 제네릭 타입을 제한하려면 extends를 사용하면 됩니다.

interface Name {
  first: string;
  last: string;
}
type DancingDuo<T extends Name> = [T, T];

const couple1: DancingDuo<Name> = [
  { first: "Fred", last: "Astaire" },
  { first: "Ginger", last: "Rogers" },
]; // 정상
const couple2: DancingDuo<{ first: string }> = [ // Name 타입에 필요한 last 속성이 없습니다.
  { first: "Sonny" },
  { first: "Cher" },
];

출처

<이펙티브 타입스크립트> (댄 밴더캅 지음, 장원호 옮김, 인사이트, 2021)

profile
what doesn't kill you makes you stronger

0개의 댓글