유틸리티 타입 ReplacePropertyValue

작성일:2025.01.10|조회수:0

유틸리티 타입 ReplacePropertyValue

라이브러리를 개발하다 보면 객체 타입의 구조는 그대로 유지하면서, 일부 프로퍼티의 값 타입만 교체해야 하는 상황이 자주 발생한다. 특히 상속이나 오버라이드 패턴을 사용해 메서드의 동작 일부를 변경하는 경우가 대표적이다. 구현 관점에서는 기존 로직을 재사용하되, 외부에 노출되는 인터페이스만 달라지는 형태다.

타입스크립트에서는 이러한 요구를 충족하기 위해 보통 Omit, Pick, Partial 같은 기본 유틸리티 타입과 인터섹션 타입을 조합한다. 예를 들어, 특정 메서드의 시그니처만 교체하고 싶은 경우에는 다음과 같은 타입을 작성하게 된다.

TS
type ReducerStore = Omit<Store<T>, "setStore"> & {
  setStore: Dispatcher;
};

이 코드는 기능적으로 아무 문제가 없다. 실제로 타입스크립트 커뮤니티에서도 널리 사용되는 패턴이다. 다만 한 가지 아쉬운 점이 있다. 이 타입이 무엇을 의도하고 있는지 한눈에 파악하기 어렵다는 것이다. Omit과 & 연산자를 해석해야만 “아, 기존 타입에서 특정 프로퍼티의 타입을 교체하려는 거구나”라는 의도를 유추할 수 있다.

이 문제를 해결하기 위해 나는 라이브러리에 ReplacePropertyValue라는 유틸리티 타입을 도입했다.

TS
type ReplacePropertyValue<
  T extends object,
  U extends { [K in keyof T]?: unknown }
> = Omit<T, keyof U> & U;

이 타입이 하는 일은 매우 단순하다. 기존 타입 T에서 U에 포함된 키들을 제거한 뒤, 다시 U와 병합한다. 즉, 결과적으로는 T의 특정 프로퍼티 값 타입만 U에 정의된 타입으로 교체하는 구조다. 동작만 놓고 보면, 앞서 작성했던 Omit + & 패턴과 정확히 동일하다.

그럼에도 불구하고 이 타입을 따로 정의한 이유는 명확하다. 이 타입은 “어떻게”보다 “무엇을” 드러낸다. ReplacePropertyValue라는 이름 자체가, 이 타입이 기존 구조를 유지한 채 일부 프로퍼티의 값 타입만 바꿔치기한다는 의도를 그대로 전달한다. 별도의 주석이 없어도, 타입 선언만 보고 충분히 맥락을 이해할 수 있다. 이를 기존 코드에 적용하면 다음과 같이 정리할 수 있다.

TS
type ReducerStore =
  Omit<Store<T>, "setStore"> & { setStore: Dispatcher };

type ReducerStore =
  ReplacePropertyValue<Store<T>, { setStore: Dispatcher }>;

두 타입은 완전히 동일하게 동작한다. 하지만 두 번째 표현은 타입을 읽는 사람에게 훨씬 친절하다. 이 코드를 처음 접하는 개발자도 “아, 이 타입은 Store<T>에서 setStore의 타입만 교체하는구나”라고 즉시 이해할 수 있다.

이처럼 커스텀 유틸리티 타입은 타입스크립트의 기능을 확장하기보다는, 의도를 드러내기 위해 존재하는 경우가 많다. 특히 라이브러리 코드처럼 여러 사람이 읽고 유지보수해야 하는 환경에서는, 타입의 가독성과 의미 전달력이 곧 코드 품질로 이어진다. ReplacePropertyValue는 그 점에서, 작은 비용으로 큰 효과를 얻을 수 있는 유틸리티 타입이라고 생각한다.

더 읽어보기

  • 2025.09.14

    Loose Autocomplete

    프로그래밍을 하다 보면 개발자가 의도한 선택지를 에디터가 자동완성으로 얼마나 잘 안내해 주느냐가 개발 경험에 큰 영향을 미친다는 사실을 자주 체감하게 된다. 특히 문자열 기반의 옵션을 다룰 때는 이 차이가 더욱 극명하게 드러난다.예를 들어 HTTP 메서드를 표현할 때 "GET" | "P…

  • 2025.04.25

    더 좁은 타입의 유효성에 대하여

    사람이 무언가를 집중해서 바라보다 보면, 어느샌가 주변부가 흐려지고 가끔은 집중하고 있던 그 대상조차 보이지 않게 된다. 처음엔 분명하게 인식되던 경계가 서서히 사라지고, 오히려 애써 무시했던 주변이 본질을 가릴 때도 있다. 잘 보려 애쓰는 행위가, 역설적으로 시야를 좁히는 순간이다.개…

  • 2025.04.06

    재귀 조건부 타입에서의 추론 컨텍스트 손실

    타입스크립트에서는 조건부 타입의 분배 과정에서도 타입 추론 컨텍스트가 유지된다. 이 특성 덕분에 단순한 분기 수준을 넘어, 상당히 복잡한 조건부 타입에서도 개발자가 의도한 방향으로 타입 추론을 유도할 수 있다. 실제로 이러한 특성은 고급 유틸리티 타입을 설계할 때 매우 강력한 도구로 작…

  • 2025.03.28

    유틸리티 타입 IsInRange

    프론트엔드 개발을 하다 보면 컴포넌트의 props로 number 타입을 받을 때가 많다. 하지만 단순히 number 타입만 지정하면 값의 범위를 제한할 수 없다는 점이 아쉽다. 예를 들어, 페이지네이션의 currentPage는 1 이상의 값만 허용해야 하고, 슬라이더의 value는 최소…

  • 2026.04.11

    Trie 자료구조

    문자열 데이터를 다룰 때 단순히 “이 단어가 있나?”만으로는 부족한 순간이 있다. 자동 완성처럼 특정 접두사로 시작하는 후보를 모아야 할 때도 있고, 어떤 키에 값을 두고 빠르게 찾고 싶을 때도 있다. 이럴 때 가장 자연스럽게 떠올릴 수 있는 자료구조가 바로 Trie이다.Trie는 무엇…

  • 2026.03.19

    Streams API 부록 2. 왜 이미지는 위에서 아래로 나타날까

    웹 페이지에서 이미지를 로드할 때 흥미로운 장면을 종종 볼 수 있다. 이미지가 한 번에 완전히 나타나는 것이 아니라, 위에서 아래로 조금씩 채워지면서 나타나는 경우가 있기 때문이다. 특히 네트워크가 느리거나 이미지가 큰 경우 이런 현상이 더 분명하게 보인다. 마치 이미지가 위쪽부터 스캔…

댓글

댓글을 불러오는 중...