타입 반복 줄이기(필독)
타입 반복
개발자라면 코드의 반복을 줄이려고 노력한 적이 분명 있을 것이다. 이런 것을 DRY(don't repeat yourself)라고 한다. 이것을 타입에도 적용하여 타입 반복 또한 줄여야 한다.
이 글은 두고두고 많이 봐야 한다고 생각한다. 왜냐하면 평소에 코드 중복을 굉장히 많이 하여 개발했기 때문이다. 그렇기에 개발을 할 때마다 이 글은 자주 챙겨보면서 타입 중복을 피하여 코딩해야겠다.
타입 반복 예시
-문제 코드
interface Person {
firstName: string
lastName: string
}
interface PersonWithBirthDate {
firstName: string
lastName: string
birth: Date
}
나도 평소에 이렇게 타입이 겹치는 것이 있더라도 따로 분리하여 타입을 중복시켰다.
타입 중복 해결 방법은 중복되는 시그니처를 명명된 타입으로 분리하거나 extends을 이용해 확장하면 된다.
-올바른 코드
interface Person {
firstName: string
lastName: string
}
interface PersonWithBirthDate extends Person {
birth: Date
}
이렇게 extends를 사용해 타입 확장을 할 수 있다.
type PersonWithBirthDate = Person & { birth: Date };
혹은 이미 존재하는 타입을 확장하면 일반적이진 않지만 인터섹션 연산자 "&"를 이용할 수 있다. 이것은 확장할 수 없는 유니온 타입에 속성을 추가할 때 유용하다.
확장이 애매한 경우(Pick)
interface State {
userId: string
pageTitle: string
recentFiles: string[]
pageContents: string
}
interface TopNavState {
userId: string
pageTitle: string
recentFiles: string[]
}
만약 State는 전체 애플리케이션의 상태이고 TopNavState는 부분만 표현하는 상태라면 TopNavState를 확장하여 State를 구성하는 것보다 State에서 속성을 빼내서 TopNavState를 구성하는 것이 좋다.
-TopNavState
interface State {
userId: string
pageTitle: string
recentFiles: string[]
pageContents: string
}
// 1번
type TopNavState = {
userId: State['userId']
pageTitle: State['pageTitle']
recentFiles: State['recentFiles']
}
// 2번
type TopNavState = {
[k in 'userId' | 'pageTitle' | 'recentFiles']: State[k]
}
맨 처음엔 1번 방식으로 줄일 수 있겠지만 결국엔 2번 방식으로 하는 것이 코드 중복을 최소화하는 코드이다.
2번 코드에 마우스를 올리면 결국 똑같은 구성을 하고 있다는 것을 알 수 있다.
이러한 방식은 배열의 필드를 루프 도는 것과 같은 방식이다. 이 패턴은 표준 라이브러리에서 볼 수 있으며 Pick라고 한다.
-Pick
type TopNavState = Pick<State, 'userId' | 'pageTitle' | 'recentFiles'>
Pick로는 이렇게 구현할 수 있다. Pick는 제네릭 타입이며 두 개의 매개변수를 받아 결괏값을 반환하여 함수를 호출하는 것과 같은 효과이다.
최소 중복으로 선택적 필드 만들기, 객체의 타입 만들기(keyof, Partial)
-keyof, Partial
keyof는 타입을 받아서 속성 타입의 유니온을 반환한다. 매핑된 타입인 [K in keyof Options]는 Options를 순회하며 K 값에 해당하는 속성이 있는지 찾는다. 이것 또한 표준 라이브러리에서 Partial이라는 이름으로 있다.
-최소 중복으로 선택적 필드 만들기
interface Options {
width: number
height: number
color: string
label: string
}
interface OptionsUpdate {
width?: number
height?: number
color?: string
label?: string
}
선택적 필드도 타입 중복을 최소화해서 만들 수 있다. 매핑된 타입과 keyof를 사용하면 Options로부터 OptionsUpdate를 만들 수 있다.
type OptionsUpdate = { [k in keyof Options]?: Options[k] }
이런 식으로 keyof를 활용하여 선택적 필드를 구현할 수 있다.
-객체의 타입 만들기
const INIT_OPTIONS = {
width: 640,
height: 480,
color: '#00FF00',
label: 'VGA',
}
type Options = typeof INIT_OPTIONS
이렇게 INIT_OPTIONS 객체를 타입으로 만들고 싶으면 typeof를 사용하면 된다.
Options에 마우스를 올리면 이렇게 타입이 나오게 된다.
함수나 메서드 반환 값으로 타입 만들기(ReturnType)
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,
}
}
type UserInfo = ReturnType<typeof getUserInfo>
이렇게 return 값의 타입을 선언하고 싶으면 ReturnType을 사용하면 된다.
이렇게 마우스를 올려보면 타입이 다 들어간 것을 볼 수 있다.