TypeScript

[TypeScript] 객체 타입의 호환성과 초과 프로퍼티 검사

2025년 11월 27일
1
TypeScriptFrontEndStudyTypeSystemInterface

📑 목차

지난 포스팅에서 "타입은 집합이다" 라는 개념을 통해 업캐스팅(자식→부모)은 가능하고, 다운캐스팅(부모→자식)은 불가능하다는 것을 배웠다.

이 원칙은 객체(Object) 타입에서도 똑같이 적용된다. 하지만 객체는 프로퍼티의 개수에 따라 부모-자식 관계가 조금 헷갈릴 수 있다. 이를 명확히 짚어보자.

1. 객체 타입의 호환성

간단한 예제로 시작해 보자.

type Animal = {
  name: string;
  color: string;
};

type Dog = {
  name: string;
  color: string;
  breed: string; // 추가된 프로퍼티
};

여기서 AnimalDog 중 어느 것이 슈퍼 타입(부모) 이고, 어느 것이 서브 타입(자식) 일까?

정답은 Animal이 슈퍼 타입이다.

얼핏 보면 Dog가 프로퍼티가 더 많으니 슈퍼 타입 같지만, 집합의 관점에서 보면 반대다.

  • Animal: namecolor만 있으면 다 포함된다. (더 넓은 범위)
  • Dog: name, colorbreed까지 있어야 한다. (더 좁고 구체적인 범위)

따라서 DogAnimal의 부분 집합(서브 타입)이 된다.

업캐스팅 적용

let animal: Animal = {
  name: "기린",
  color: "yellow",
};

let dog: Dog = {
  name: "돌돌이",
  color: "brown",
  breed: "진도",
};

animal = dog; // ✅ OK (업캐스팅: Dog -> Animal)
// dog = animal; // ❌ NO (다운캐스팅: Animal -> Dog)

dog 변수는 Animal이 필요로 하는 namecolor를 모두 가지고 있다. 따라서 Animal 타입으로 취급해도 아무런 문제가 없다. 이것이 구조적 타이핑(Structural Typing) 의 핵심이다.


2. 또 다른 예시 (Book)

이해를 돕기 위해 예제 하나를 더 보자.

type Book = {
  name: string;
  price: number;
};

type ProgrammingBook = {
  name: string;
  price: number;
  skill: string;
};

여기서도 Book이 슈퍼 타입, ProgrammingBook이 서브 타입이다.

let book: Book;
let programmingBook: ProgrammingBook = {
  name: "한 입 크기로 잘라먹는 리액트",
  price: 33000,
  skill: "reactjs",
};

book = programmingBook; // ✅ OK
// programmingBook = book; // ❌ NO

3. 예외: 초과 프로퍼티 검사 (Excess Property Check)

그런데 타입스크립트에는 업캐스팅임에도 불구하고 에러를 발생시키는 특수한 규칙이 있다. 바로 초과 프로퍼티 검사다.

문제 상황

위의 Book 예제를 그대로 사용하여, 변수에 객체 리터럴을 직접 할당해 보자.

let book2: Book = {
  name: "한 입 크기로 잘라먹는 리액트",
  price: 33000,
  skill: "reactjs", // ❌ 에러 발생! (Object literal may only specify known properties...)
};

"어? skill이 있어도 nameprice가 있으니까 Book 타입에 들어갈 수 있는 거(업캐스팅) 아닌가?"

논리적으로는 맞다. 하지만 타입스크립트는 변수를 초기화할 때 '객체 리터럴'을 직접 사용하면, 타입에 정의되지 않은 초과된 프로퍼티가 있는지 엄격하게 검사한다. 이를 방지하기 위한 안전장치다.

해결 방법: 변수에 담아서 할당하기

이 검사는 객체 리터럴을 직접 대입할 때만 발동한다. 따라서 값을 다른 변수에 미리 담아둔 뒤 할당하면 검사를 피할 수 있다.

// 미리 만들어둔 변수 (ProgrammingBook 타입으로 추론되거나 명시됨)
let programmingBook = {
  name: "한 입 크기로 잘라먹는 리액트",
  price: 33000,
  skill: "reactjs",
};

// 변수를 할당하면 초과 프로퍼티 검사가 발동하지 않음
let book3: Book = programmingBook; // ✅ OK

함수 인수의 경우

함수에 인수를 전달할 때도 동일하게 적용된다.

function func(book: Book) {}

// 1. 객체 리터럴 직접 전달 -> 검사 발동 (에러)
func({
  name: "리액트",
  price: 33000,
  // skill: "reactjs", // ❌ 에러 발생
});

// 2. 변수에 담아서 전달 -> 검사 회피 (성공)
func(programmingBook); // ✅ OK

요약

  1. 객체 타입 호환성: 프로퍼티가 더 적은 쪽이 슈퍼 타입(부모), 더 많은 쪽이 서브 타입(자식) 이다.
  2. 구조적 타이핑: 서브 타입 객체를 슈퍼 타입 변수에 할당하는 것(업캐스팅)은 언제나 가능하다.
  3. 초과 프로퍼티 검사: 단, 객체 리터럴을 직접 할당할 때는 타입에 없는 속성이 있으면 에러가 난다. 이를 피하려면 변수에 담아서 할당하면 된다.
@taemni

@taemni

안녕하세요, 차근차근 성장 중인 조태민입니다.

instagram

댓글