[TYPESCRIPT] Readonly와 const assertion - 불변성을 타입으로 보장하는 방법

 

실무에서 발생하는 많은 버그는 의도하지 않은 값 변경에서 시작됩니다. TypeScript는 이러한 문제를 컴파일 단계에서 막을 수 있도록 Readonly 유틸리티 타입const assertion(as const)이라는 강력한 기능을 제공합니다.

이 두 가지를 제대로 이해하면, 설정 값·상태 값·상수 데이터·도메인 모델을 안전하게 고정된 형태로 설계할 수 있습니다.

 

Readonly란?

Readonly<T>는 객체 타입의 모든 프로퍼티를 읽기 전용(readonly)으로 변환하는 유틸리티 타입입니다.

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

type ReadonlyUser = Readonly<User>;
// {
//   readonly id: number;
//   readonly name: string;
// }

Readonly로 변환된 타입은 할당 이후 수정이 불가능합니다.

const user: ReadonlyUser = { id: 1, name: "Alice" };
user.name = "Bob"; // 컴파일 에러

주요 사용 사례:

  • 변경되면 안 되는 도메인 데이터
  • 초기화 이후 고정되는 설정 정보
  • 외부에 노출되는 불변 객체

 

ReadonlyArray — 배열 불변성

배열에 대해서는 ReadonlyArray<T>를 사용합니다.

const roles: ReadonlyArray<string> = ["admin", "user"];

roles.push("guest"); // X
roles[0] = "guest";  // X

단, map, filter, concat처럼 새 배열을 반환하는 메서드는 정상적으로 사용할 수 있습니다.

 

const assertion(as const)이란?

as const는 값 전체를 가장 좁은 리터럴 타입 + readonly로 고정하는 문법입니다.

const status = "success" as const;
// 타입: "success"

일반적인 경우라면 status: string으로 추론되지만, const assertion을 사용하면 리터럴 타입으로 고정됩니다.

 

객체에 const assertion 적용

const config = {
  retry: 3,
  mode: "debug",
} as const;

위 객체의 타입은 다음과 같습니다.

{
  readonly retry: 3;
  readonly mode: "debug";
}

특징 정리:

  • 모든 프로퍼티가 readonly
  • number/string이 아닌 리터럴 타입으로 고정
  • 값과 타입이 완전히 일치

 

const assertion과 유니온 타입 생성

실무에서 가장 많이 사용되는 패턴 중 하나입니다.

const STATES = ["idle", "loading", "success", "error"] as const;

type State = typeof STATES[number];
// "idle" | "loading" | "success" | "error"

enum 없이도 타입 안전한 상태 모델을 만들 수 있으며, Discriminated Union과 함께 사용하면 매우 강력합니다.

 

Readonly vs const assertion 차이

구분 Readonly<T> as const
적용 대상 타입
불변성 범위 프로퍼티 readonly 값 + 타입 전체 고정
리터럴 타입화 X O
주 사용 목적 API/도메인 타입 보호 상수·상태·옵션 정의

요약하면 다음과 같습니다.

  • 값 자체가 상수다 → as const
  • 타입 구조만 불변으로 만들고 싶다 → Readonly

 

실무에서 자주 쓰는 패턴

설정 객체 보호

const APP_CONFIG = {
  apiTimeout: 3000,
  env: "production",
} as const;

옵션 목록 + 타입 자동 생성

const ROLES = ["ADMIN", "USER"] as const;
type Role = typeof ROLES[number];

함수 인자 보호

function process(user: Readonly<User>) {
  // user는 읽기 전용
}

 

주의사항

  • as const는 변경 가능해야 하는 객체에 사용하면 오히려 불편
  • React state, mutable 도메인에는 신중히 적용
  • 불변성이 필요한 경계(boundary)에만 사용하는 것이 이상적

불변성은 강력하지만, 모든 곳에 적용하는 것은 오히려 독이 될 수 있습니다.

 


 

Readonly와 const assertion은 TypeScript에서 의도를 타입으로 표현하는 가장 중요한 수단입니다. 이 두 가지를 잘 활용하면 코드의 신뢰도와 유지보수성이 크게 향상됩니다.

  • Readonly → 타입 레벨 불변성
  • as const → 값 + 리터럴 타입 + 불변성
  • 상태·설정·상수 모델링의 핵심 도구