성장, 그리고 노력

부족하더라도 어제보다 더 잘해지자. 노력은 절대 배신하지 않는다.

Typescript

더 나은 타입스크립트 작성하기 1 - Skills

제이콥(JACOB) 2019. 12. 27. 08:30
DRY - "Don't Repeat Yourself"

 타입 스크립트 사용에 대한 글은 이전에도 작성했고, 웹 상에도 수많은 글이 존재한다. 심지어 공식 문서 또한 친절하게 잘 나와있다. 그래서 이번 글은 말 그대로 더 나은 타입 스크립트를 작성하기 위한 요령(skills)을 담았다. 

 

중복을 피하자 1

 개발자라면 누구나 귀가 닳도록 들었을 말이다. 중복을 피하는건, 가독성이나 코드의 유지 보수 시에 매우 중요한 역할을 한다. 근데 이상하게 타입 스크립트를 사용하면 이 말을 피해 가는 경우를 종종 보곤 한다. 예를 들어보자.

 API에서 받아오는 User 모델로 Person과 PersonDetails 타입 두 개를 만들어 보자.

type Person = {
  name: string;
  age: number;
};

type PersonDetails = {
  name: string;
  age: number;
  address: string;
};

여기서 중복을 피하는 방법은 두 가지가 있다.

첫번째 방법은 가장 일반적인 방법인 interface를 상속 받는 방법이다.

interface Person {
  name: string;
  age: number;
}

interface PersonDetails extends Person {
  address: string;
}

두 번째 방법이 내가 소개하고 싶은 좀 더 심플한 방법이면서, 타입을 그대로 사용하는 방법이다. 바로 & (intersection operator)를 사용하는 것이다.

type Person = {
  name: string;
  age: number;
};

type PersonDetails = Person & { address: string };

중복을 피하자 2

 그렇다면 반대의 경우는 어떻게 해야 할까? 예를 들어 좀 더 포괄적인 모델 Person이 존재하고 하위 모델로 PersonSummary가 존재하며, 여기서 사용되는 키는 동일한 값이라고 해보자.

type Person = {
  name: string;
  age: number;
  address1: string;
  address2: string;
  gender: string;
  image: string;
  hobby: string[];
};

type PersonSummary = {
  name: string;
  gender: string;
  image: string;
};

물론 위에서 한 것처럼 중복을 줄일 수는 있겠지만, 그러면 논리가 이상해져서, 모델이 엉망이 될 가능성이 많다. 

이 경우 중복을 줄일 수 있는 방법은 세 가지가 있다.

 

첫 번째 방법은 Person 타입의 인덱스를 이용해서 접근하는 방법이다.

type Person = {
  name: string;
  age: number;
  address1: string;
  address2: string;
  gender: string;
  image: string;
  hobby: string[];
};

type PersonSummary = {
  name: Person["name"];
  gender: Person["gender"];
  image: Person["image"];
};

이 방법 또한 유용한 경우야 있겠지만은 지금은 우리의 코드를 개선시키지 못했다. 왜냐하면 중복이 여전히 존재하고 있기 때문이다. 

 두 번째 방법은 타입 매핑(mapped types, 매핑된 타입을 의역)을 이용하는 것이다.

type Person = {
  name: string;
  age: number;
  address1: string;
  address2: string;
  gender: string;
  image: string;
  hobby: string[];
};

type PersonSummary = {
  [k in "name" | "gender" | "image"]: Person[k];
};

중복도 제거되었고, 더 직관적인 코드가 되었다. 

마지막 세 번째 방법은 두 번째 방법을 타입스크립트에서 제공해 주는 Pick으로 대체해 보겠다.

// Syntax
type Pick<T, K> = { [k in K]: T[k] };

문법을 한글로 풀어보면, "T에서 K 속성(properties)을 고른다(pick)"라는 의미이다.

type Person = {
  name: string;
  age: number;
  address1: string;
  address2: string;
  gender: string;
  image: string;
  hobby: string[];
};

type PersonSummary = Pick<Person, "name" | "gender" | "image">;

 


 

중복을 피하자 3

 이번에는 웹페이지에 흔히 있는 "개인정보 수정"을 Person 모델을 이용해서 만들려고 한다. 아래 예제부터 보자.

type Person = {
  name: string;
  age: number;
  address1: string;
  address2: string;
  gender: string;
  image: string;
  hobby: string[];
};

type PersonUpdate = {
  name?: string;
  age?: number;
  address1?: string;
  address2?: string;
  gender?: string;
  image?: string;
  hobby?: string[];
};

 기본적으로 사용하는 Person모델과 개인정보 수정시에 사용할 PersonUpdate모델을 만들었다. 그리고 PersonUpdate 모델은 해당 프로퍼티가 없을 수도 있기 때문에 모두 옵션 처리하였다. 딱 봐도 수많은 중복이 존재하지만 옵션 처리 때문에 이리저리 못하고 있는 상황이다. 이때 위에서 사용한 매핑 타입keyof를 사용하여 중복을 제거할 수 있다.

 그전에 간단하기 때문에 keyof에 대해 설명하겠다.

// Syntax
keyof T

한글로 해석해보면, "T의 key를 union 타입으로 반환해 준다"이다. 예를들면, keyof Person은 아래와 같다.

 자, 이제 본론으로 돌아와서 위에 중복이 가득했던 코드의 중복을 제거해 보자.

type Person = {
  name: string;
  age: number;
  address1: string;
  address2: string;
  gender: string;
  image: string;
  hobby: string[];
};

type PersonUpdate = { [k in keyof Person]?: Person[k] };

기존 코드와 비교해 보면 정말 "헉" 소리가 난다. 실제로 확인해 봐도 타입이 완벽하게 적용된 걸 확인할 수 있다.

그런데 이것도 타입스크립트에서 제공해 주는 Partial <T>로 대체할 수 있다.

type Person = {
  name: string;
  age: number;
  address1: string;
  address2: string;
  gender: string;
  image: string;
  hobby: string[];
};

type PersonUpdate = Partial<Person>;

 

반응형