타입스크립트 코드를 작성할 때 초과 프로퍼티 검사(Express Property Checking)가 발생하는 경우와 발생하지 않는 경우에 대해 정리해보자.

초과 프로퍼티 검사 예시

TypeScript  // Animal은 슈퍼타입
  type Animal = {
    name: string, 
    age: number
  }

  // Cat은 서브타입
  type Cat = {
    name: string,
    age: number,
    color: string
  }

  let cat1: Cat = {
    name: "야옹이",
    age: 1,
    color: "yellow"
  }
  • Animalname, age 두 개의 프로퍼티를 가진다.
  • Catname, age, color 세 개의 프로퍼티를 가진다.
  • cat1Cat 타입이므로 3가지 프로퍼티를 모두 명시해야한다.
  • Animal 타입의 변수를 만들고 cat1을 할당하는 경우와,
    Animal 타입의 변수에 객체리터럴로 name, age, color 를 직접 넣는 경우를 비교해보자.
TypeScript  let animal1: Animal;
  animal1 = cat1; // 정상적으로 할당 됨 (구조적 타입 시스템)

  let animal2: Animal = {
    name: "네로",
    age: 2,
    color: "black" // 에러: 초과 프로퍼티 검사
  }
  • 결과: 객체리터럴로 직접 할당한 animal2 에서는 color 프로퍼티가 초과로 판단되어 에러가 발생한다.

초과 프로퍼티 검사란?

  • TypeScript는 구조적 타입 시스템이므로 "필요한 프로퍼티가 있으면(최소 요건 충족)" 대입이 가능하다.
  • 다만 객체 리터럴을 바로 타입에 할당하는 경우에는 실수(오타 등) 방지를 위해 "초과 프로퍼티 검사"를 추가로 수행한다.
  • animal1 = cat1; 이 가능한 이유는 Animal이 요구하는 최소 요건(name, age)을 만족하기 때문이다.
    -> 타입 간 관계를 이름/상속이 아니라 프로퍼티 구조로 판단한다.
  • 반면, animal2처럼 객체 리터럴을 바로 할당하면, TypeScript는 이를 "fresh object literal"로 보고, 다음을 엄격하게 검사한다.
    -> "지금 선언한 타입(Animal)에 없는 프로퍼티가 섞여 있으면, 오타/실수 가능성이 크다"
    -> 예: colr같은 오타, 잘못된 필드명을 잡아내기 위한 안전장치이다.

즉, 초과 프로퍼티 검사는 "할당 불가능 규칙"이라기보다 객체 리터럴에만 추가로 붙는 "실수 방지용 검사"에 가깝다.
정리하면,

  • 객체 리터럴 직접 할당 => "초과 프로퍼티 검사" 트리거
  • 변수/표현식(이미 이름 붙은 값) 할당 => 기본 구조 검사만 적용 (초과 검사는 트리거되지 않음)

초과 프로퍼티를 고려한 3가지 패턴

A. 초과 프로퍼티 검사를 "의도적으로" 우회하는 패턴

TypeScriptconst cat6 = {name: "야옹", age: 2, color: "brown"}
let animal3: Animal = cat6; // OK

B. Animal을 만족하는지 검사하되, 추론 타입은 유지한다. (satisfies)

TypeScript  const animal4 = {
    name: "야옹이", 
    age: 5, 
    // color: "black" 포함하면 에러 발생
  } satisfies Animal;

-> Animal을 만족하는지 검사하지만, 객체의 실제 타입 추론은 유지됨

C. 추가 프로퍼티가 필요하면 인덱스 시그니처 사용

TypeScript  type Animal2 = {
    name: string;
    age: number;
    [key: string]: unknown;              
  }

-> 인덱스 시그니처를 사용하면 추가 프로퍼티를 허용할 수 있다.

-> 단, 초과 프로퍼티 검사를 사실상 무력화하므로 오타 검출 능력이 약해질 수 있어 신중히 사용한다.