extends와 intersection
기존 타입에 속성을 더 붙일 때 interface extends와 type &를 둘 다 쓸 수 있는데, 프로퍼티 타입이 충돌하는 상황에서 처리 방식이 다릅니다.
핵심 내용
extends는 부모와 자식의 프로퍼티 타입이 충돌하면, 선언하는 그 자리에서 바로 컴파일 에러가 납니다. 컴파일러가 상속 관계를 보고 있으니까 충돌을 즉시 잡습니다.
&는 선언 시점엔 에러를 안 냅니다. 대신 충돌하는 프로퍼티를 내부적으로 never로 만들어버립니다. 타입 자체는 만들어지지만, 실제로 그 타입의 값을 만들려는 순간 에러가 뜹니다.
같은 충돌인데 에러가 뜨는 시점이 다릅니다. extends는 선언 시점, &는 사용 시점. 이 차이 때문에 &로 충돌을 만들어두면 원인을 찾기 어려워집니다. 에러가 뜨는 곳이 실제 문제가 생긴 타입 정의가 아니라 한참 떨어진 사용처이기 때문입니다.
예시
interface OriginalMenuItem {
id: string;
price: number;
}
interface CartMenuItem extends OriginalMenuItem {
id: number; // 에러: 'id' 속성의 형식이 호환되지 않습니다
}
type OriginalMenuItem = {
id: string;
price: number;
};
type ConflictingItem = {
id: number;
};
type Merged = OriginalMenuItem & ConflictingItem; // 에러 없이 통과
const item: Merged = { id: "1", price: 1000 }; // 에러: id는 never 형식
헷갈리기 쉬운 점
&가 선언 시점엔 에러 없이 통과하기 때문에, 타입 정의만 보고 "문제없다"고 착각하기 쉽습니다. 실제로는 그 타입을 사용하는 시점까지 가봐야 문제가 드러납니다.
실무에서 볼 점
- 기존 객체 타입을 확장하면서 충돌을 빨리 잡고 싶다면
extends가 원인을 찾기 쉽습니다. - 여러 타입을 조합해야 해서
&를 쓸 때는 충돌한 프로퍼티가never가 되지 않는지 확인합니다.
예상 질문
extends와 &는 타입 충돌을 언제 잡나요?
extends는 선언 시점에 충돌을 바로 잡고, &는 충돌한 프로퍼티를 never로 만든 뒤 사용 시점에 드러납니다.
그래서 상속 관계를 명확히 표현할 때는 extends가 원인을 찾기 쉽고, &는 유니온이나 유틸리티 타입 조합처럼 교차 타입이 필요한 상황에서 쓰는 편이 낫습니다.