🏷️

TypeScript 타입 시스템 — 컴파일 후 전부 사라진다

구조적 타이핑(Structural Typing)이 뭔지, 왜 Java의 명목적 타이핑과 다른지

interface Cat { name: string; meow(): void; }
interface Dog { name: string; meow(): void; }  // Cat과 구조가 같다

const cat: Cat = { name: 'Kitty', meow() {} };
const dog: Dog = cat;  // 에러 안 남! 구조가 같으니까

Java에서는 CatDog가 이름이 다르면 호환 불가. TypeScript에서는 프로퍼티와 메서드의 구조가 같으면 호환된다.

컴파일 후 뭐가 남는가

// TypeScript
function greet(name: string): string {
  return `Hello, ${name}`;
}

// 컴파일 후 JavaScript
function greet(name) {
  return `Hello, ${name}`;
}

: string이 전부 사라졌다. TypeScript 컴파일러(tsc)는 타입 체크 후 타입 정보를 전부 지운다(type erasure). 런타임에서 typeof로 TypeScript 타입을 확인할 수 없는 이유.

이게 문제가 되는 경우

interface User { name: string; age: number; }

// 런타임에서 API 응답이 User인지 검증하려면?
const data = await fetch('/api/user').then(r => r.json());
// data as User ← 이건 캐스팅이지 검증이 아니다!

TypeScript 타입은 런타임에 없으니까 검증할 수 없다. Zod나 Pydantic 같은 런타임 검증 라이브러리가 필요한 이유.

타입 좁히기(Type Narrowing)

런타임에서 타입을 확인하려면 JavaScript의 방법을 쓴다:

if (typeof x === 'string') { /* x는 string */ }
if ('name' in obj) { /* obj에 name 프로퍼티 있음 */ }
if (x instanceof Date) { /* x는 Date */ }

TypeScript 컴파일러가 이런 패턴을 인식하고 그 분기 안에서 타입을 좁혀준다.

핵심 포인트

1

TypeScript 타입은 컴파일 시점에만 존재 — 런타임 JS에 흔적 없음 (type erasure)

2

구조적 타이핑 — "이름"이 아닌 "구조"로 호환성 판단

3

API 응답 등 런타임 검증은 Zod/Valibot 같은 별도 라이브러리 필요

4

typeof/in/instanceof 패턴으로 Type Narrowing 가능

사용 사례

API 응답 검증 — Zod + TypeScript로 컴파일 타임 + 런타임 모두 커버 라이브러리 타입 정의 — .d.ts 파일이 왜 별도로 필요한지 이해