지식
TypeScript
readonly와 const 단언 — 불변 타입 설계
TypeScript readonly 수정자, ReadonlyArray, Readonly 유틸리티, as const 리터럴 타입 좁힘을 완전히 정리합니다.
지난 글에서 인덱스 시그니처를 살펴봤다. 이번에는 TypeScript의 불변(immutable) 타입 도구인 readonly와 as const를 다룬다. 데이터가 변경되지 않음을 타입 수준에서 명시하면 버그를 줄이고 코드 의도를 명확히 할 수 있다.
readonly 프로퍼티
interface Config {
readonly host: string;
readonly port: number;
timeout: number; // mutable
}
const config: Config = { host: "localhost", port: 3000, timeout: 5000 };
config.host = "example.com"; // TS2540 ❌ readonly 프로퍼티
config.timeout = 10000; // OK
readonly는 컴파일 타임 전용이다. 런타임에는 실제로 변경을 막지 않는다 — 단지 TypeScript가 경고를 줄 뿐이다.
ReadonlyArray
배열 변경 메서드를 막으려면 ReadonlyArray<T> 또는 readonly T[]를 사용한다.
function process(items: readonly string[]) {
items.push("new"); // TS2339 ❌ push는 readonly 배열에 없음
items[0] = "changed"; // TS2542 ❌ 인덱스 할당 불가
return items.map(s => s.toUpperCase()); // OK — 새 배열 반환
}
Readonly 유틸리티 타입
Readonly<T>는 타입의 모든 프로퍼티를 readonly로 만든다.
interface User {
id: number;
name: string;
email: string;
}
type ReadonlyUser = Readonly<User>;
// { readonly id: number; readonly name: string; readonly email: string }
// 깊은 불변성은 별도 처리 필요 (중첩 객체는 적용 안 됨)
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
as const — 리터럴 타입 좁힘
as const는 값의 타입을 가능한 한 구체적인 리터럴 타입으로 좁힌다.
// as const 없음
const config = {
env: "production", // string
port: 3000, // number
};
// as const 있음
const config = {
env: "production", // "production" (리터럴)
port: 3000, // 3000 (리터럴)
} as const;
// 모든 프로퍼티가 readonly + 리터럴 타입
배열에서 유니언 만들기
as const의 가장 강력한 용도 중 하나다.
const COLORS = ["red", "green", "blue"] as const;
// COLORS: readonly ["red", "green", "blue"]
type Color = typeof COLORS[number]; // "red" | "green" | "blue"
function setColor(color: Color) { /* ... */ }
setColor("red"); // OK
setColor("yellow"); // TS2345 ❌
배열을 as const로 선언하고 typeof arr[number]로 유니언 타입을 추출하면, 배열과 타입이 항상 동기화된다.
열거형 대안으로서의 as const
enum보다 가볍게 쓸 수 있는 패턴이다.
const Direction = {
Up: "UP",
Down: "DOWN",
Left: "LEFT",
Right: "RIGHT",
} as const;
type Direction = typeof Direction[keyof typeof Direction];
// "UP" | "DOWN" | "LEFT" | "RIGHT"
enum과 달리 as const 객체는 일반 JavaScript 객체로 컴파일되며, 트리 쉐이킹이 잘 작동한다.
지난 글: 인덱스 시그니처 — 동적 키 타입 처리
다음 글: keyof와 typeof — 타입에서 키를 추출하다
읽어주셔서 감사합니다. 😊