hooninedev.com
🧩
프론트엔드TypeScript

as const 한 줄로 만드는 타입 안정성

2025.06.20

프론트엔드 실무에서 as const를 단순히 타입 추론을 돕는 보조 수단이 아니라, 불변성 선언과 동시에 타입의 리터럴화를 통해 안정성과 자동완성을 극대화할 수 있는 툴로 유용하게 사용하고 있다.

이번 글에서는 실무에서 as const의 활용 사례를 중심으로 활용과 원리까지 깊이 있게 다뤄보려 한다.


as const

as const는 TypeScript에서 값을 리터럴 타입으로 고정(literal narrowing) 하기 위한 단언문이다.

const button = "primary";         // 타입: string
const button = "primary" as const; // 타입: "primary"

배열이나 객체에 쓰면 해당 구조를 읽기 전용(readonly)으로 만들면서 각 요소의 타입을 리터럴로 고정한다.

const status = ["idle", "loading"] as const;
// 타입: readonly ["idle", "loading"]

이처럼 값을 변경 불가능한 상수로 명시하고, 타입도 좁게 고정되도록 만드는 것이 핵심이다.


TanStack Query – query key 안정성 확보

const queryKey = ["user", userId] as const;
useQuery({ queryKey, queryFn });

여기서 as const를 쓰지 않으면 queryKey는 (string | number)[]로 추론되어 타입 안정성이 떨어진다.

반면 as const를 쓰면 queryKey가 정확히 readonly ["user", string]이 되어, query key가 고정된 식별자로 취급되고, 캐싱/비교/인밸리데이션에서도 더 안정적으로 작동한다.


React Router – 라우팅과 같은 객체 구조의 키값 고정

const router = createBrowserRouter([
  { path: "/home", element: <HomePage /> },
  { path: "/about", element: <AboutPage /> }
] as const);

React Router는 내부적으로 라우트 배열을 순회하면서 타입을 추론하려고 하기 때문에, as const로 선언해주면 각 path의 리터럴 값("/home", "/about")을 정확히 추론할 수 있다.

이를 통해 라우트 기반 자동완성, 타입 기반 네비게이션 제어가 가능해진다.


as const를 쓰지 않아야 할 때

무조건 좁은 타입이 좋은 건 아니다. 다음과 같은 경우는 오히려 as const를 쓰지 않는 것이 낫다.

let status = "idle" as const;

이렇게 하면 status는 절대 다른 문자열을 가질 수 없기 때문에, 동적으로 상태를 변경해야 하는 경우엔 적절치 않다.


function update(type: "create" | "delete") {}
update("create" as const); // 불필요

함수 시그니처가 리터럴 타입을 받는 경우, 이미 문자열 리터럴을 넣으면 타입이 자동으로 좁혀진다. 명시적 as const는 오히려 코드만 장황해질 수 있다.


내부 동작 원리 – const assertion의 타입화

TypeScript에서 as const는 해당 값은 readonly로 간주되고, 가능한 한 좁은 타입으로 리터럴 추론을 한다.

const obj = { a: 1, b: "x" } as const;
// 타입: { readonly a: 1; readonly b: "x"; }

이는 일종의 const assertion으로, 값 자체는 JS에서 변하지 않지만 타입도 함께 고정시키는 것이 특징이다.

배열, 객체, 튜플 등에 모두 적용 가능하며, 유형 안정성(type safety)을 명시적으로 선언하는 수단이다.


기술적으로 조금 더 깊게 들어가면

TypeScript는 as const를 만나면 해당 값 전체를 readonly로 처리하며, 내부 속성 하나하나를 리터럴 타입으로 고정한다.

이는 내부적으로 const T: { readonly a: 1, readonly b: "x" } 같은 형태로 변환된다.

제네릭 함수에서 as const된 인자를 넘기면, 추론된 타입이 정확한 리터럴 형태로 유지되기 때문에 타입 제한, 자동완성, 추론 정확도가 크게 향상된다.

특히 readonly 튜플로 바뀐다는 점에서, 배열 기반 추론(예: query key 등)에 매우 큰 이점을 준다.

const key = ["user", 1] as const;
type Key = typeof key; // readonly ["user", 1]

만약 as const 없이 선언했다면 Key(string | number)[]이 되었을 것이고, 타입 정합성은 깨졌을 것이다.


enum과 비슷하지만 다르다.

as constenum은 모두 값 집합을 고정시키는 용도로 사용되지만, 아래와 같은 차이가 있다.


항목 as const enum
런타임 존재 여부 값 그 자체 객체 생성됨
타입 유추 방식 리터럴 타입으로 추론 명시적 타입 선언 필요
Tree-shaking 가능 어려움
JS 출력 코드 없음 존재 (실제 객체 생성)
type Mode = (typeof MODES)[number];

const MODES = ["light", "dark"] as const;

enum ModeEnum {
  Light = "light",
  Dark = "dark"
}

대부분의 경우 불필요한 런타임 비용 없이 타입만 고정하고 싶을 때는 as const가 더 효율적이다.

© Powered by hoonine