타입 중복은 간과하기 쉽지만 코드 중복만큼 많은 문제를 일으키며, 불필요하게 코드 라인을 늘리게 되는 원인 중 하나이기도 하다.
interface Person {
firstName: string;
lastName: string;
}
interface PersonWithBirthDate {
firstName: string;
lastName: string;
birth: Date;
}
위 예시를 살펴보자. Person 타입과 PersonWithBirthDate 타입은 언뜻 보기에도 굉장히 유사하다.
PersonWithBirthDate를 Person의 확장개념으로 보면 아래와 같이 중복을 제거할 수 있게 된다.
interface PersonWithBirthDate extends Person {
birth: Date;
}
타입 반복을 줄이는 방법
1. 타입에 이름 붙이기
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 distacnce(a: Point2D, b: Point2D) {...}
2. 명명된 타입으로 분리하기
function get(url: string, opt: Options): Promise<Response> {...}
function post(url: string, opt: Options): Promise<Response> {...}
2개 이상의 함수가 같은 타입의 시그니처를 공유하고 있다면, 명명된 타입으로 변경해서 중복된 코드를 줄일 수 있다.
type HTTPFunction = (url: string, opt:Options) => Promise<Response>;
const get:HTTpFunction = (url, opt) => {...};
const post:HTTpFunction = (url, opt) => {...};
3. 부분집합으로 타입 정의하기
interface State {
userId: string;
pageTitle: string;
recentFiles: string[];
pageFContents: string;
}
interface TopNavState {
userId: string;
pageTitle: string;
recentFiles: string[];
}
이 예제 역시 TopNavState를 확장해서 State로 선언해서 중복을 줄일 수도 있다. 그렇지만, 네이밍을 보면 State가 조금 더 범용적으로 사용되는 범위임을 알 수 있다. 오히려 State의 부분집합으로 TopNavState를 사용할 수 있으면 더 좋겠다는 생각이 든다.
이 때는 State를 인덱싱 하여 속성 타입에서 중복을 제거할 수 있다.
방법1)
type TopNavState = {
userId; State['userId'];
pageTitle: State['pageTitle'];
recentFiles: State['recentFiles'];
}
흠... 여전히 변경될 수 있는 요소들이 보인다.
매팅된 타입을 이용해서 조금 더 간결하게 작성해보자.
방법2)
type TopNavState = {
[k in 'userId' | 'pageTitle' | 'recentFiles']: State[k]
}
매핑된 타입 => 배열의 필드를 루프도는 것과 동일한 방식이다. 이러한 패턴은 타입스크립트에서 기본으로 제공하고 있으며
Pick을 사용하면 동일한 기능을 구현할 수 있다.
방법3)
type Pick<T, k> = { [k in K]: T[K]}
type TopNavState = Pick<State, 'userId' | 'pageTitle' | 'recentFiles'>;
4. 태그된 유니온에서 중복발생 시
interface SaveAction {
type: 'save';
}
interface LoadAction {
type: 'load'
}
type Action = SaveAction | LoadAction;
typeActionType = 'save' | 'load' //타입의 반복
typeActionType = Action['type'] //'save | 'load'
5. 매핑된 타입과 keyof를 이용해서 중복 없애기
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) {...}
}
update 매개변수의 타입은 생성자 타입과 동일하면서, 타입이 선택적 필드가 되는 것인데,
이를 매핑 타입과 keyof 를 사용해서 Options로부터 OptionsUpdate를 만들수 있다.
type OptionsUpdate = {
[k in keyof Options]?: Options[k];
}
//keyof Options: 'width' | 'height' | 'color' | 'label';
keyof Options 👉 매핑된 타입, Options를 순회하면서 k 값에 해당하는 속성이 있는지 찾는다.
=> 타입스크립트에서 지원해주는 속성을 활용하자 : Partial!
type Partial<T> = {
[P in keyof T]?: T[P];
};
Partial을 이용하면 다음과 같이 간결하게 코드를 수정할 수 있다.
type OptionsUpdate = Partial<Options>
6. 값의 형태에 해당하는 타입 정의하기
const INIT_OPTIONS = {
width: 644,
height: 400,
color: '#ff00ee',
label: 'VGA'
}
type Options = typeof INIT_OPTIONS;
/*
type Options = {
width: number;
height: number;
color: string;
label: string;
}
*/
런타임 되기 전, 타입 체크 단계에서 typeof 연산자가 사용되며 (자바스크립트의 런타임 연산자 typeof 가 아님.) 더 정확한 타입을 표현한다.
*유의사항 : 선언 순서에 주의할 것! : (선) 타입정의 → (후) 타입 할당 가능 선언
7. 함수or 메서드 반환 값에 명명된 타입 지정하기
ReturnType 활용하기
function getUserInfo(userId: string){
return {
userId,
name,
age,
height,
weight,
favoriteColor,
}
}
type UserInfo = ReturnType<typeof getUserInfo>
UserInfo 타입에 getUserInfo 함수의 타입을 ReturnType으로 지정하여 할당하였다.
8. 제네릭 타입에서 매개변수 제한하기
→ extends 사용하기, 제네릭 매개변수가 특정타입을 확장한다고 선언할 수 있다.
interface Name {
first: string;
last: string;
}
type DancingDuo<T extends Name> = [T, T];
const couple1: DancingDuo<Name> = [
{first: 'Joy', last: 'Joshua'},
{first: 'Ginger', last: 'Rogers'}
]
const couple2:DancingDuo<{first: string}> = [
{first: 'Sony'},
{first: 'Alex'}
]
//🚨 오류 발생! '{first: string; }' 유형에 'last' 속성이 없습니다
copule2에서 매개변수 {first: string}는 Name 타입을 확장하지 않기 때문에 오류가 발생한다. (Name의 부분집합, 상속개념이 아님)
임의로 ParamsType을 설정해서 오류를 수정해보았다.
type ParamsType = {middle?: string} & Name;
const couple2:DancingDuo<ParamsType> = [
{first: 'Sony', last: 'Smith'},
{first: 'Alex', last: 'James', middle: 'Saint'}
]
# 참고 : Pick 올바르게 사용하기
type Pick<T, K> = {
[k in K]: T[k]
//🚨 'K' 타입은 'string | number | symbol' 타입에 할당할 수 없음
}
//to-be ~ 'K' 타입의 범위를 좁히기
type Pick<T, K extends keyof T> = {
[k in K]: T[k]
}
// extend는 '확장'이 아닌 '부분집합' 개념이라는 걸 이해하자
'Frontend > Typescript' 카테고리의 다른 글
러닝 타입스크립트 - 3장. 유니언과 리터럴 (0) | 2023.07.04 |
---|---|
러닝 타입스크립트 - 2장. 타입 시스템 (0) | 2023.07.01 |
아이템 16. number 인덱스 시그니처보다는 Array, 튜플, ArrayLike 사용하기 (0) | 2023.04.23 |
아이템 15. 동적 데이터에 인덱스 시그니처 사용하기 (0) | 2023.04.22 |
[TS] 함수 타입 설정 (0) | 2022.09.05 |