[TYPESCRIPT] as const를 쓰기 시작하면 타입이 고정되는 지점들이 보이기 시작한다

 

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는 타입 축소의 출발점으로 두고, 그 이후의 흐름은 코드가 자연스럽게 설명하도록 두는 편이 오래 유지되는 경우가 많았습니다.