[타입시스템 14] 반복 줄이기(타입연산, 제네릭)
2022. 7. 19. 20:35ㆍ책/이펙티브 타입스크립트
1. 반복에 대하여
- 일반적인 코드를 작성할 때 반복하지 말라(DRY원칙)는 말이 있듯 타입 또한 반복을 피해야 한다
- 문제는 타입의 반복을 어떻게 줄여야 할지 감도 안잡힌 다는 것이다(익숙치 않으니까)
- TS는 반복 문제를 해결하기 위해 다양한 도구를 제공하고 있으니 여기에 익숙해져야 한다
2. 반복 줄이기
2.1 타입에 이름을 붙여라
- 반복되는 변수, 상수들을 찾아서 뽑아내라
- 함수가 같은 타입 시그니처를 공유하는지 확인해라
// 2D 거리 계산하기
function getDist(
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 Vector2D {
x :number;
y :number;
}
function getDist2(a :Vector2D, b :Vector2D) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2))
}
2.2 extends를 활용하라
- 유사한 프로퍼티를 가진 타입이라면 extends를 통한 확장으로 반복 제거가 가능하다
- 경우에 따라선 인터섹션 연산자(&)를 활용할 수도 있다(흔치는 않지만)
interface Person {
firstName :string;
lastNAme :string;
}
interface PersonBirth {
firstName :string;
lastName :string;
birth :Date;
}
// 유사한 프로퍼티를 공유하고 있다면 extends로
// 반복을 줄이면서 적절히 확장해나가자
interface PersionBirth extends Person {
birth :Date;
}
2.3 매핑된 타입을 활용하라
- A mapped type : 프로퍼티키들을 순회하면서 타입을 생성하는 제네릭 타입
generic type which uses a union of PropertyKeys (frequently created via a keyof) to iterate through keys to create a type - 배열의 필드를 루프 도는 것과 비슷한 방식이다(Array.forEach( val => { ... } );
- TS 표준 라이브러리엔 매핑된 타입을 쉽게 쓸 수 있도록 제네릭 타입이 마련되어 있다(Pick, Partial, ReturnType, ... )
interface State {
userId :string;
pageTitle :string;
recentFiles :string[];
pageContents :string;
}
// 이렇게 State에서 필요한 키를 뽑아오는
// 즉, 인덱싱을 활용하는 방법도 있지만
interface NavBarState {
userId :State['userId'];
pageTitle :State['pageTitle'];
recentFiles :State['recentFiles'];
}
// 이렇게 매핑된 타입으로 우아하게
// 정의할 수도 있다
type NavbarState = {
[k in 'userId' | 'pageTitle' | 'recentFiles'] :State[k]
};
2.4 제네릭 타입(유틸리티 타입)
◎ Pick<T, K>
// 이렇게 손수 써준 우아한 타입을
type NavbarState = {
[k in 'userId' | 'pageTitle' | 'recentFiles'] :State[k]
};
// 제네릭 타입으로 더 쉽게 쓸 수 있다
type NavbarState = Pick<State, 'userId' | 'pageTitle' | 'recentFiles'>;
◎ Partial<T>
<T>의 모든 프로퍼티를 선택적 키로 삼는 타입을 생성한다
interface Opts {
width :number;
height :number;
color :string;
label :string;
}
interface UpdateOpts {
width? :number;
height? :number;
color? :string;
label? :string;
}
// 반응형으로 UI를 건드리는 위젯이 있다고 치자
// 넓이가 변할지, 길이가 변할지, 뭐가 변할지 아무도 몰루
// 즉, 거의 모든 필드가 선택적이 됨
class UIwidget {
constructor(init :Opts) {
// ...
}
update(opts :UpdateOpts) {
// ...
}
}
// 이 방법도 충분히 우아하지만
type UpdateOps = {
[k in keyof Opts]? :Opts[K];
};
// 이렇게 더 간단히 쓰는것도 가능
type UpdateOps2 = Partial<Opts>
◎ ReturnType<T>
함수의 타입을 정의한 <T>에서 함수가 반환하는 값의 타입을 추출한다
function getUserInfo(userId :string) {
return {
userId,
email,
company,
role,
};
}
// 함수가 뱉어내는 '타입'을 추출하고 싶은거니까
// getUserInfo함수에 typeof를 써준다
type UserInfo = ReturnType<typeof getUserInfo>
3. 제네릭 타입 주의사항
함수에서 파라미터로 매핑할 수 있는 값을 제한하듯 제네릭 타입에서도 파라미터를 제한할 수 있는 방법이 필요하다. 예를 들어, ReturnType<T>에서 <T>는 string이나 number만 받고싶다면 이를 제한할 적절한 방법을 찾아야 한다는 뜻이다.
그리고 이 때 extends를 활용할 수 있다.
interface Name {
first :string;
last :string;
}
type UserNames<T extends Name> = T[];
const userNames :UserNames<Name> = [
{ first: '군', last: '김' },
{ first :'군', last: '나' },
{ first :'군', last: '박' },
]
'책 > 이펙티브 타입스크립트' 카테고리의 다른 글
[타입시스템 17] readony 변경 방지 (0) | 2022.07.20 |
---|---|
[타입시스템 15] 인덱스 시그니처 (0) | 2022.07.19 |
[타입시스템 13] Type vs Interface (0) | 2022.07.18 |
[타입시스템 12] 함수 표현식에 타입 적용하기 (0) | 2022.07.18 |
[1장] 타입스크립트 알아보기(2/2) (0) | 2022.07.13 |