[TYPESCRIPT] 타입 확장과 중복 방지 패턴 - 유지보수성을 극대화하는 TypeScript 실무 전략

프로젝트가 커질수록 TypeScript 타입 선언은 점점 복잡해지고, 비슷한 타입을 반복해서 선언하다 보면 중복이 늘어나며 유지보수 비용이 커집니다. 이 문제를 해결하기 위해 실무에서는 타입 확장(extends)중복을 줄이는 패턴을 적극적으로 활용합니다.

 

 

기본 개념: 타입 확장(extends)이란?

타입 확장은 객체 타입을 재사용하며 새로운 속성을 추가할 때 사용하는 방식입니다. Java의 상속 개념과 유사하지만 훨씬 유연합니다.

type User = {
  id: number;
  name: string;
};

type Admin = User & {
  role: "admin";
};

여기서 Admin은 User의 모든 속성을 상속받고, role 속성만 추가했습니다. 교차 타입(&)이 가장 널리 쓰이는 확장 방식입니다.

 

인터페이스 확장하기 (extends)

인터페이스에서는 extends 키워드를 이용해 확장이 가능합니다.

interface User {
  id: number;
  name: string;
}

interface Admin extends User {
  role: "admin";
}

인터페이스는 선언 병합도 가능하기 때문에 도메인 모델에서 확장성을 높일 때 자주 사용됩니다.

 

중복 방지 패턴 1 — 공통 타입을 Base로 분리하기

여러 타입에서 반복되는 공통 속성이 있다면 Base 타입으로 따로 분리합니다.

type BaseEntity = {
  id: number;
  createdAt: string;
  updatedAt: string;
};

type User = BaseEntity & {
  name: string;
};

type Article = BaseEntity & {
  title: string;
  content: string;
};

이 패턴은 DB 엔티티, API 응답 모델에서 특히 많이 사용합니다.

 

중복 방지 패턴 2 — Pick, Omit 활용

기존 타입에서 특정 속성만 선택하거나 제외해 새로운 타입을 만들 수 있습니다.

✔ Pick

type User = {
  id: number;
  name: string;
  age: number;
  email: string;
};

// 특정 필드만 선택
type UserProfile = Pick<User, "id" | "name">;

✔ Omit

type UserWithoutEmail = Omit<User, "email">;

API 요청/응답에서 필요 없는 필드를 제거하는 용도로 많이 쓰입니다.

 

중복 방지 패턴 3 — Partial / Required / Readonly

✔ Partial: 모든 속성을 optional로 변환

type UserUpdate = Partial<User>;

✔ Required: 모든 속성을 필수로 변환

type StrictUser = Required<User>;

✔ Readonly: 속성 수정 방지

type FrozenUser = Readonly<User>;

공통 타입을 활용해 다양한 변형 타입을 만들면서도 타입 선언 중복을 방지할 수 있는 핵심 패턴입니다.

 

중복 방지 패턴 4 — Mapped Types(매핑된 타입)

객체의 key 전체를 대상으로 일괄적으로 타입 변환을 수행하는 방식입니다.

type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

type UserNullable = Nullable<User>;

매핑된 타입은 여러 속성에 동일한 규칙을 적용할 때 중복을 완전히 제거해 줍니다.

 

중복 방지 패턴 5 — 유틸리티 타입 + keyof + typeof 조합

const roles = {
  admin: 1,
  user: 2,
  guest: 3,
} as const;

type RoleKey = keyof typeof roles;   // "admin" | "user" | "guest"
type RoleValue = (typeof roles)[RoleKey]; // 1 | 2 | 3

역할(Role) 같은 상수 데이터를 유지하면서 하나의 선언로 key와 value 유니온 타입을 모두 생성할 수 있기 때문에 중복 선언이 아예 발생하지 않습니다.

 

실무 예제: API 요청/응답 타입 재사용

✔ 공통 타입 설계

type BaseResponse = {
  success: boolean;
  timestamp: number;
};

✔ 응답 타입 확장

type UserResponse = BaseResponse & {
  data: {
    id: number;
    name: string;
  };
};

type ArticleResponse = BaseResponse & {
  data: {
    id: number;
    title: string;
  };
};

API 공통 필드를 BaseResponse로 분리하면 모든 API 응답 모델에서 중복이 사라지고 유지보수가 쉬워집니다.

 

불필요한 중복을 줄이기 위한 실무 규칙

  • 반복되는 속성은 반드시 Base 타입으로 분리
  • 공통 로직의 타입 변형은 Partial/Omit/Pick으로 해결
  • 객체 상수는 as const로 선언 후 typeof/keyof로 타입 자동 생성
  • 도메인별 타입은 작게 설계하고 교차(&)로 확장
  • 중첩 객체는 매핑된 타입으로 변환 처리
  • 타입 단언 대신 타입 가드를 활용해 타입 안정성 확보

이 규칙들을 적용하면 프로젝트 규모가 커져도 타입 선언이 난잡해지지 않고 도메인 중심으로 정리된 안정적인 타입 구조를 유지할 수 있습니다.

 


 

TypeScript의 타입 확장 기능과 중복 방지 패턴을 이해하면 타입 정의가 반복되는 상황을 크게 줄일 수 있으며, 프로젝트의 확장성과 유지보수성을 높일수 있습니다.

  • & / extends → 타입 확장 핵심
  • Pick / Omit / Partial → 중복 제거 필수 도구
  • keyof + typeof → 상수 기반 타입 자동화
  • 매핑된 타입 → 구조적 변환의 정답