Allra Fintech

타입 시스템 기초: 타입은 계산 규칙이다

TypeScript 타입을 문법 표기로 보지 않고, JS로 변환되는 컴파일 산출물을 통제하는 타입 시스템으로 이해한다.

이 문서의 목표

TypeScript를 배우는 핵심은 type/interface 문법 암기가 아니라, 이 아니라 타입 규칙이 설계 의도를 전달한다는 점을 이해하는 것입니다.

이 문서는 백엔드 개발자 관점에서 보면 가장 익숙한 3가지를 중심으로 설명합니다.

  • 합타입(Union)으로 상태의 분기를 나타내는 법
  • 곱타입(Intersection/Object)으로 값의 성분을 조합하는 법
  • 컴파일 단계에서 타입이 사라지고, JS는 언제나 런타임 규칙으로 남는다는 점

1) TypeScript는 “컴파일 타임 타입 체크 언어”다

TypeScript 코드:

type Money = { amount: number };
type ApiStatus<T> = { ok: true; data: T } | { ok: false; error: string };

const getMoney = (): ApiStatus<Money> => ({ ok: true, data: { amount: 1000 } });

실제 JS 출력(요약):

const getMoney = () => ({ ok: true, data: { amount: 1000 } });
  • 타입 표기(type, : number, Money, ApiStatus<T>)는 모두 지워짐
  • 타입은 컴파일러에게 계약을 전달하고, 런타임으로는 실행 규칙이 들어가지 않음

그래서 타입 = 런타임 동작이 아니라 타입 = 정적 안전성 규칙입니다.

2) 합타입(Union): 가능한 상태의 목록

A | B는 “A 또는 B 중 하나”를 뜻합니다.

type ApiResult =
  | { status: "success"; data: string }
  | { status: "error"; message: string };

장점:

  • 실패/성공 상태를 명시적으로 분기
  • if (result.status === "success") 같은 분기점에서 안전하게 데이터 접근

백엔드 관점에서 보면 API 응답의 200/400/500을 프론트 상태로 모델링한 것과 유사합니다.

3) 곱타입(Intersection): 성분의 결합

A & B는 A의 성분과 B의 성분을 동시에 만족합니다.

type BaseUser = { id: string };
type VipTag = { isVip: boolean };

type VipUser = BaseUser & VipTag;

실무 해석:

  • 객체 계약을 작게 쪼개고 조합해서 재사용
  • 공통 스키마(BaseUser) + 기능별 첨부(VipTag)를 누적 가능

4) 타입 합성의 사고법: “곱(필수 성분) / 합(분기)” 분리

  • UI 상태는 대개 합타입이 먼저 적합: idle | loading | success | error
  • 공통 도메인 데이터는 곱타입이 먼저 적합: id + createdAt + name

이 분리를 먼저 잡으면, “이 값은 무엇이냐?”보다 “이 값의 상태 조합이 어떤 의미냐?”를 먼저 설계하게 됩니다.

5) 제네릭과 타입 분기(컴파일 타임)

type Data<T> = T extends string
  ? { kind: "text"; value: T; length: number }
  : { kind: "other"; value: T };

type A = Data<"ok">; // { kind: "text"; value: "ok"; length: number }
type B = Data<{ id: string }>; // { kind: "other"; value: { id: string } }

Data<T>의 결과 타입은 T에 따라 타입 검사 시점(컴파일 타임)에서 결정됩니다.
트랜스파일된 런타임 JavaScript에는 Textends 분기 정보가 남지 않습니다.

비교:

  • TypeScript의 조건부 타입은 타입 검사기에서 계산되는 규칙이고, JavaScript로 내려가면 타입은 소거됩니다.
  • Java/C#/Rust 등은 제네릭과 타입 제약을 제공하지만, TypeScript의 T extends U ? X : Y처럼 타입을 조건식으로 평가해 새로운 타입을 계산하는 조건부 타입 문법은 제공하지 않습니다.

6) 실무에서 자주 생기는 함정

  • 타입을 runtime으로 착각하고 typeofT를 가려보려 함
  • 합타입에서 상태 태그(status) 없이 분기
  • 실패/빈값을 하나로 null에 묶어 버리고 화면에서 예측 불가 버그 발생

체크리스트

  • “이 값은 합타입인지 곱타입인지” 판단해 설계할 수 있는가?
  • 태그 기반 합타입(예: kind, status)으로 분기 누락이 컴파일 타임에 걸리도록 설계할 수 있는가?
  • 제네릭을 “컴파일 타임 규칙”으로 설명할 수 있는가?
  • 타입은 JS 출력에서 사라진다는 점을 팀원에게 설명할 수 있는가?