본문 바로가기
C.W.K.
Stream
Lesson 02 of 05 · published

인터섹션 타입: 여러 계약을 동시에 만족하기

~9 min · unions-intersections, intersection, composition

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"유니언이 여러 가능성 가운데 하나라면, 인터섹션은 여러 요구를 한 값이 모두 지켜야 한다는 뜻이야."

A이면서 B

A & B는 A의 멤버와 B의 멤버를 모두 가진 값을 나타내. 객체 타입에서는 여러 작은 계약을 조합해 하나의 큰 모양을 만들 수 있어. User & Timestamped라면 사용자 필드와 생성·수정 시각을 모두 가져야 해.

순서는 의미에 영향을 주지 않지만 같은 이름의 속성이 서로 호환되지 않으면 문제가 생겨. { name: string } & { name: number }의 name은 string이면서 number여야 하므로 사실상 never가 돼. 타입 선언 자체는 남아도 실제 값을 만들 수 없는 모양이 된다.

조합과 상속의 차이

interface의 extends도 객체 계약을 합칠 수 있어. 이름 있는 객체 계층을 열어 확장하려면 interface가 잘 읽히고, 유니언·조건부 타입을 포함한 임의의 타입 조각을 결합하려면 인터섹션이 더 유연해. 어느 쪽이 더 객체지향적인지보다 관계가 어떻게 읽히는지가 중요해.

교차 관심사를 붙여

식별 가능, 감사 기록, 시간 정보처럼 여러 도메인 객체에 선택적으로 붙는 작은 능력을 별도 타입으로 만들고 인터섹션으로 조합할 수 있어. 거대한 기본 인터페이스 하나에 모든 선택 속성을 넣는 것보다 각 계약의 경계가 선명해진다.

브랜드 타입의 밑바탕

type UserId = number & { __brand: 'UserId' }처럼 원시 타입과 고유 표식을 교차하면 일반 number와 구별되는 ID 타입을 만들 수 있어. 실행 중에는 숫자지만 컴파일러는 생성 함수를 거쳐 얻은 UserId만 받게 할 수 있지.

브랜드는 단언을 아무 데나 붙이면 무너져. 검증하거나 생성하는 한정된 함수가 브랜드를 부여해야 의미가 있어. 타입 트릭이 도메인 경계를 대신하는 게 아니라 경계를 표현하는 도구라는 점을 잊지 마.

유니언은 OR, 인터섹션은 AND. 서로 다른 가능성을 나열할 때와 여러 계약을 조합할 때를 분명히 갈라.

피파의 고백

인터섹션을 믹스인처럼 계속 쌓으면 처음엔 조립식이라 예뻐 보여. 그런데 충돌한 속성이 never가 된 뒤 오류는 멀리서 나타나지. 작은 계약을 조합하되 결과 모양을 사람이 읽을 수 있는지 꼭 확인해.

Code

Intersection — composition 과 branding·typescript
// Intersection — 모양 결합.
type User = { id: number; name: string };
type Timestamped = { createdAt: Date; updatedAt: Date };
type Audited = { auditTrail: string[] };

type AuditedUser = User & Timestamped & Audited;
// AuditedUser 가짐: id, name, createdAt, updatedAt, auditTrail

const u: AuditedUser = {
  id: 1, name: 'Pippa',
  createdAt: new Date(), updatedAt: new Date(),
  auditTrail: [],
};

// Intersection 통한 branded 타입.
type UserId = number & { __brand: 'UserId' };
const id = 1 as UserId;
const raw: number = id;       // ✅ number 로 widening
const back: UserId = raw;     // ❌ raw number 가 UserId 에 assignable 안 됨

External links

Exercise

Identifiable = { id: number }, Named = { name: string }, Timestamped = { createdAt: Date }를 정의하고 세 타입을 교차한 Entity를 만들어. 이어서 IdentifiableNamed를 확장하는 User 인터페이스를 작성해 Timestamped를 뺀 Entity와 같은 모양인지 확인해.
Hint
구체 타입을 조합할 때는 교차 타입이 간결해. 각 부분에 이름과 확장 관계가 있다면 인터페이스 상속이 더 잘 읽혀.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.