프로젝트가 커질수록 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 → 상수 기반 타입 자동화
- 매핑된 타입 → 구조적 변환의 정답
