[TYPESCRIPT] 객체 타입 정의하기 - TypeScript의 구조적 타입 시스템 완전 이해

TypeScript의 가장 강력한 기능 중 하나는 바로 객체(Object) 타입 정의입니다. JavaScript에서 객체는 자유롭게 속성을 추가하거나 제거할 수 있지만, 이는 예기치 않은 런타임 오류를 만들어내는 주요 원인이 됩니다. TypeScript는 객체의 구조를 명확히 정의하고 타입 검사를 수행함으로써, 안정성과 유지보수성을 크게 향상시킵니다.

 

기본 객체 타입 선언

가장 단순한 형태의 객체 타입은 속성과 그 타입을 지정하는 방식입니다.

let user: {
  name: string;
  age: number;
  isAdmin: boolean;
};

user = {
  name: "Alice",
  age: 30,
  isAdmin: true,
};

TypeScript는 필수 속성 누락, 타입 불일치를 모두 체크해 줍니다.

user = { name: "Bob" };
// ❌ 오류: age, isAdmin 속성이 없어서 오류 발생

 

선택적(Optional) 속성

특정 속성이 필수는 아닐 때 ? 를 사용해 선택적 속성을 만들 수 있습니다.

let product: {
  title: string;
  price: number;
  description?: string; // 선택적 속성
};

product = { title: "Keyboard", price: 49000 };
product = { title: "Mouse", price: 29000, description: "Wireless" };

선택적 속성이 존재하지 않아도 오류가 발생하지 않으며, 사용시 undefined 여부 체크가 필요할 수 있습니다.

 

읽기 전용(Readonly) 속성

객체 내 특정 속성을 변경할 수 없도록 강제하려면 readonly를 사용합니다.

let config: {
  readonly appName: string;
  version: string;
};

config.appName = "New Name";
// ❌ 오류: 읽기 전용 속성은 수정할 수 없음

정적 설정값, 환경 변수, 고정 ID 등에 매우 유용합니다.

 

객체 타입 재사용: Type Alias

여러 곳에서 반복되는 객체 구조는 Type Alias로 정의할 수 있습니다.

type User = {
  id: number;
  name: string;
  email?: string;
};

let u1: User = { id: 1, name: "Alice" };
let u2: User = { id: 2, name: "Bob", email: "bob@test.com" };

Type Alias는 데이터를 구조적으로 재사용할 때 필수적인 기능입니다.

 

인터페이스(Interface) - 객체 타입의 확장

interface는 객체 타입 정의를 위한 또 다른 방식이며, 특히 OOP 스타일의 확장(extends)에 강력한 기능을 제공합니다.

1. 기본 인터페이스

interface User {
  id: number;
  name: string;
  isAdmin: boolean;
}

const admin: User = {
  id: 1,
  name: "Root",
  isAdmin: true
};

2. 인터페이스 확장

interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

const myDog: Dog = {
  name: "Buddy",
  breed: "Golden Retriever"
};

상속 기반 구조에서 인터페이스가 특히 유리합니다.

 

타입(alias) vs 인터페이스(interface) 차이

구분 type interface
확장성 일반적으로 확장 불가(교차 타입으로 가능) extends로 확장에 최적화
유연성 union, tuple 등 다양한 타입 표현 객체 타입 정의 중심
중복 선언 오류 자동 병합

실무에서는 아래 기준으로 선택하면 됩니다:

  • 객체 중심 상황 → interface
  • 유연한 타입 조합 필요 → type

 

객체 타입과 인덱스 시그니처

객체의 key/value 구조가 동적일 때 인덱스 시그니처를 활용합니다.

interface StringMap {
  [key: string]: string;
}

const headers: StringMap = {
  "Content-Type": "application/json",
  Authorization: "Bearer token"
};

실무에서 API 요청 헤더, 다국어(i18n) 번역 데이터에 자주 사용됩니다.

 

객체 타입의 readonly + optional 조합

둘을 함께 사용하면 “수정은 금지, 존재 여부는 선택적”인 속성을 정의할 수 있습니다.

interface Profile {
  readonly id: number;
  nickname?: string;
}

const p: Profile = { id: 1 };
p.nickname = "KakaoVX"; // 가능
p.id = 2;  // ❌ 오류

 

실무에서 자주 사용되는 객체 타입 패턴

1. API 응답폼

interface ApiResponse {
  success: boolean;
  data: T;
  message?: string;
}

2. DB 모델

interface UserEntity {
  id: number;
  email: string;
  createdAt: string;
  updatedAt: string;
}

3. 상태 관리(Store) 구조

type AuthState = {
  token: string | null;
  user?: User;
};

이처럼 객체 타입은 실제 애플리케이션에서 가장 많이 사용되는 구조입니다.

 

 

TypeScript의 객체 타입 정의는 단순히 “타입을 붙이기”가 아니라, 코드의 의도를 명확히 하고 유지보수성을 극대화하는 설계 도구입니다. 객체의 구조가 복잡해질수록 인터페이스와 타입 alias를 적절히 조합해 관리하는 것이 중요합니다.