TypeScript에서 타입이 넓어지는 순간은 생각보다 자주 발생합니다. 객체 리터럴을 선언했는데 string으로 추론되거나, 배열을 만들었을 뿐인데 요소 타입이 모두 같은 형태로 묶여버립니다.
처음에는 “타입 추론이 좀 아쉽네” 정도로 넘어가지만, 조건 분기나 매핑 로직이 붙기 시작하면 이 차이가 바로 체감됩니다. 이 지점에서 자주 등장하는 키워드가 as const입니다.
이 글에서는 as const를 단순한 문법 설명이 아니라, 실무 코드에서 타입을 어디까지 고정하고, 어디서 풀어주는 게 나았는지를 중심으로 정리합니다.
타입이 넓어지는 가장 흔한 경우
다음 코드는 전혀 이상해 보이지 않습니다.
const STATUS = {
ACTIVE: 'ACTIVE',
INACTIVE: 'INACTIVE',
};
하지만 이 객체의 타입을 확인해 보면, 값은 모두 string으로 추론됩니다.
type Status = typeof STATUS[keyof typeof STATUS];
// string
상태 값으로 쓰기에는 너무 넓습니다. ACTIVE와 INACTIVE만 허용하고 싶은데, 타입은 그 의도를 전혀 반영하지 못합니다.
as const를 붙였을 때 달라지는 점
같은 코드에 as const를 붙이면 결과가 바로 달라집니다.
const STATUS = {
ACTIVE: 'ACTIVE',
INACTIVE: 'INACTIVE',
} as const;
이제 값은 더 이상 string이 아니라, 리터럴 타입으로 고정됩니다.
type Status = typeof STATUS[keyof typeof STATUS];
// 'ACTIVE' | 'INACTIVE'
이 시점부터 타입은 “아무 문자열”이 아니라 “정해진 값 중 하나”가 됩니다.
배열에서 체감되는 차이
배열에서도 같은 문제가 반복됩니다.
const roles = ['admin', 'user', 'guest'];
이 배열의 타입은 string[]입니다. 인덱스로 꺼내면 항상 string으로 취급됩니다.
as const를 붙이면 상황이 달라집니다.
const roles = ['admin', 'user', 'guest'] as const;
type Role = typeof roles[number];
// 'admin' | 'user' | 'guest'
이 패턴은 권한, 상태, 이벤트 타입처럼 값의 범위가 명확한 경우에 자주 쓰입니다.
조건 분기에서 드러나는 효과
타입이 좁혀지면 조건 분기도 달라집니다.
function handleStatus(status: Status) {
if (status === 'ACTIVE') {
// 여기서는 'ACTIVE'로 고정
return;
}
// 여기서는 'INACTIVE'
}
as const 없이 string으로 남아 있었다면, 이 분기는 타입 차원에서는 아무 의미가 없었을 겁니다.
객체를 설정 값으로 쓸 때
설정 객체를 만들 때도 as const는 자주 등장합니다.
const config = {
retry: 3,
mode: 'safe',
};
이 상태에서는 mode가 string으로 추론됩니다. 의도는 분명히 'safe'인데, 타입에서는 그 정보가 사라집니다.
const config = {
retry: 3,
mode: 'safe',
} as const;
이제 mode는 'safe'로 고정됩니다. 설정 값이 코드 전체에 영향을 주는 구조에서는 이 차이가 꽤 크게 느껴집니다.
너무 고정했을 때 생기는 불편함
as const는 강력하지만, 모든 곳에 쓰기 시작하면 바로 불편해집니다.
const params = {
page: 1,
size: 20,
} as const;
이제 page와 size는 각각 1과 20으로 고정됩니다. 조금만 코드를 수정하려 해도, 타입 에러가 먼저 튀어나옵니다.
이런 값은 “의미가 고정된 값”이 아니라 “변할 수 있는 상태”에 가깝습니다. 이 경우에는 타입을 좁히는 게 오히려 방해가 됩니다.
as const와 타입 축소의 역할 분리
실무에서 오래 남는 방식은 비교적 명확합니다.
- 의미 집합을 표현하는 상수에는 as const
- 상태나 입력 값에는 일반 타입 추론 유지
- 축소는 조건 분기에서 자연스럽게 유도
as const는 타입 축소를 시작하게 만드는 도구이고, 실제 축소는 if, switch 같은 흐름 제어에서 이루어집니다. 이 역할이 뒤섞이면 코드가 경직됩니다.
운영 코드에서 느껴지는 차이
as const를 적절히 쓴 코드에서는,
- 허용되지 않는 값이 컴파일 단계에서 걸러지고
- 조건 분기가 자연스럽게 타입과 맞물리고
- 의미 없는 string 비교가 줄어듭니다
반대로 과하게 쓰인 경우에는, 작은 변경에도 타입 에러가 연쇄적으로 발생합니다. 이때부터는 타입이 보호막이 아니라 제약처럼 느껴집니다.
as const는 타입을 더 정확하게 만드는 문법입니다. 하지만 정확하다는 게 항상 좋은 건 아닙니다.
값의 의미가 고정되어 있고, 그 범위를 좁히는 게 이득인 경우에만 효과를 봅니다. 변화가 전제된 값까지 고정하면, 타입은 의도를 설명하기보다 움직임을 막게 됩니다.
as const는 타입 축소의 출발점으로 두고, 그 이후의 흐름은 코드가 자연스럽게 설명하도록 두는 편이 오래 유지되는 경우가 많았습니다.
'개발 > Typescript' 카테고리의 다른 글
| [TYPESCRIPT] 선언 병합을 쓰게 되는 순간과 그 이후에 남는 것들 (0) | 2026.02.18 |
|---|---|
| [TYPESCRIPT] Extract와 ReturnType을 쓰다 보면 생기는 활용과 한계 (0) | 2026.02.17 |
| [TYPESCRIPT] 조건부 타입의 분배가 시작되면 타입이 예상과 다르게 움직인다 (0) | 2026.02.15 |
| [TYPESCRIPT] 함수 커링을 쓰기 시작하면 타입이 먼저 보이기 시작한다 (0) | 2026.02.14 |
| [TYPESCRIPT] 브랜딩 타입으로 값의 의미를 구분하려다 마주치는 지점들 (0) | 2026.02.13 |
