나는 지금까지 나름 타입스크립트를 열심히 쓰고있었다 생각했었는데, 진짜 열심히만 쓰고 잘 쓰고있지는 않았나보다. 타입스크립트의 유틸리티 타입을 이제서야 알았으니..
나는 타입스크립트를 사용해오면서 분~명히 이렇게 정의하는게 맞는건가 뭔가 미심쩍었지만, 그냥 에러만 안뜨면 된다 라고 생각하고 타입정의를 상당히 바보같이 하고 있었다. 하지만 이번에야말로 지저분한 타입정의 코드를 고쳐잡기 위해 유틸리티 타입을 알아보고 사용해보려고한다. 유틸리티 타입은 타입스크립트를 사용할 때 공통 타입 변환을 용이하게 하기 위해서 제공되는 타입이라고 타입스크립트 핸드북에서 설명하고 있다. 찾아보니 너무나 다양한 유틸리티 타입이 있었다. 근데 3개만 자주 사용한다고해서 3개만 포스팅할거임. 다른 유틸리티 타입은 차차 알아가보도록하고 Partial, Pick, Omit 이 3가지 유틸리티 타입을 알아봐보자.
Partial<Type>
Partial<Type>은 어떤 Type을 정의한 모든 프로퍼티를 선택적으로 만드는 타입을 생성한다.
이 유틸리티 타입을 알고 딱 들었던 생각이 있었다. '아니 이런 개꿀 기능이 있었단 말이야..?' 항상 여러 프로퍼티 중에서 선택적으로 업데이트 시켜줘야하는 프로퍼티가 있을 때 그에 맞는 타입정의를 계속 새로 해줬었다. 하지만 Partial<Type>을 사용하여 쉽게 타입을 생성해줄 수 있었다. 코드의 예시를 보자.
interface Todo {
title: string;
description: string;
}
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}
const todo1 = {
title: "이것은 투두리스트입니다잉",
description: "초기 설명입니다잉",
};
const todo2 = updateTodo(todo1, {
description: "업데이트된 설명입니다잉",
});
// 위 코드에서 todo2의 값 (todo1에 todo2 속성 업데이트)
todo2 : {
"title": "이것은 투두리스트입니다잉",
"description": "업데이트된 설명입니다잉"
}
위의 코드처럼 Todo라는 타입을 정의해주고 초기의 todo를 업데이트시켜주는 updateTodo 함수를 만들어서 사용을 하려고할 때 description만 업데이트해주는 것처럼 부분만 업데이트를 시켜주는 경우가 무조건 있을 것이다. 그럴 때 유용하게 사용할 수 있는 유틸리티 타입이 바로 Partial<Type>이다. Partial<Todo>를 통해서 Todo라는 타입의 프로퍼티 중에서 일부분만 가지고 있는 타입을 반환받아서 사용할 수 있는 것이다. Todo라는 타입의 프로퍼티를 모두 가져도 되고, 아무것도 안가져도 에러가 발생하지 않지만, Todo의 프로퍼티가 아닌 프로퍼티를 사용할 때는 에러가 발생한다.
쉽게 한줄요약 - Partial<T>는 제네릭 타입 T의 모든 프로퍼티를 선택적으로 만든 새로운 타입을 생성.
하지만, 모든 속성 타입을 옵셔널로 만들기 때문에 아래에서 설명할 Pick이나 Omit 유틸리티 타입을 사용하는 것이 더 좋다.
Pick<Type, Key>
위에서 설명한 모든 프로퍼티를 옵셔널로 만든 타입을 생성해주는 Partial<T> 유틸리티 타입과는 다르게 Pick<Type, Key>는 제네릭 Type에서 특정 프로퍼티(제네릭 Key)만 선택하여 타입을 새로 만들어내는 유틸리티 타입이다. 쉽게 말하면, 프로퍼티 전체를 옵셔널로 만들지 않고, 새로 만들 타입의 프로퍼티를 골라서 타입을 반환받아서 사용할 수 있는 것이다. 코드의 예시를 보자.
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, 'title' | 'completed'>;
const todo: TodoPreview = {
title: '타입스크립트 공부',
completed: false,
};
위의 코드처럼 Pick<Type, Key>에서 Type에 Todo 타입을 넣어주고, 이 타입의 Key에 해당하는 프로퍼티를 선택하여 따로 모아 타입을 생성해준다. 타입 Todo에는 title, description, completed 3개의 프로퍼티가 있다. 이 중에서 title, completed 2개의 프로퍼티만 선택하여 따로 모은 타입 TodoPreview가 생성된 것이다. 이제 TodoPreview 타입을 사용하여 todo변수를 선언하면 title과 completed 프로퍼티만을 가지는 객체가 되는 것이다.
쉽게 한줄요약 - Pick<T, K>는 제네릭 타입 T로부터 K에 해당하는 프로퍼티를 선택하여 따로 모아 타입을 생성.
Omit<Type, Key>
Pick<T, K>가 특정 프로퍼티를 선택하여 따로 모아 타입을 생성하는 유틸리티 타입이였다면, Omit<T, K>는 그 반대버전이라고 생각하면 된다. Omit<T, K>는 기존 타입에서 특정 프로퍼티를 제거한 새로운 타입을 생성하는 유틸리티 타입이다. Pick<T, K>에서는 title, completed 2개의 프로퍼티를 가진 타입을 생성해주기 위해 각각 2개의 프로퍼티의 Key를 선택하였다면, Omit<T, K>에서는 title, description, completed 전체 프로퍼티 중에서 description 프로퍼티의 Key를 제거한 타입을 생성하게 되는 것이다. 코드를 예시로 보면,
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Omit<Todo, 'description'>;
const todo: TodoPreview = {
title: '타입스크립트 공부',
completed: false,
};
위처럼 Omit<Type, Key>에서 기존 타입 Todo에서 특정 프로퍼티 description을 제거한 새로운 타입 TodoPreview을 생성하는 역할로 사용할 수 있다. Pick<T, K>에서와 똑같이 TodoPreview 타입을 사용하여 todo변수를 선언하면 title과 completed 프로퍼티만을 가지는 객체가 되는 것이다.
쉽게 한줄요약 - Omit<T, K>는 제네릭 타입 T로부터 K에 해당하는 프로퍼티를 제거한 타입을 생성.
나의 순수(?)했던 과거 코드
내 과거의 타입정의 코드를 보는데 눈물이 나온다.. 내가 작성한 수많은 타입정의 중에 하나를 가져와보았다. 이런식으로 liked라는 프로퍼티가 있냐 없냐에 따라 타입정의를 하나하나씩 새로 해주었던 나는.. 하지만 이렇게 정의도 해봄으로써 유틸리티 타입에 대한 고마움과 편리함을 금방 알 수 있어서 오히려 좋아(?)
export interface tabAType {
data: {
reviewBoardId: number;
title: string;
movieTitle: string;
liked: boolean;
user: {
userId: number;
nickname: string;
profileImage: string;
};
}[];
}
export interface tabBType {
data: {
reviewBoardId: number;
title: string;
movieTitle: string;
user: {
userId: number;
nickname: string;
profileImage: string;
};
}[];
}
이렇게 자주 쓰일법한 타입스크립트 유틸리티 타입 3가지에 대해서 공부해보았다. 지금까지의 나는 항상 프로퍼티가 조금조금씩 다른 타입정의를 계속해서 하드코딩으로 새로 만들어주고있었는데, 이런 기가막힌 유틸리티 타입을 사용하여 타입 생성을 쉽게 할 수 있을 것 같다. 사용하기 편하라고 만들어놓은건 자주 이용하자!