중복되는 부분에 타입을 붙여 반복을 줄일 수 있습니다.
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"
함수나 메서드의 반환 값에 명명된 타입을 만들고 싶을 수도 있습니다.
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)