iBetter Books
수정

Ch 05. 타입 단언과 as const

타입 단언(type assertion)은 개발자가 컴파일러보다 타입을 더 잘 안다고 선언하는 방법입니다. 강력하지만 남용하면 TypeScript의 보호막을 스스로 제거하는 꼴이 됩니다. as const는 반대로 컴파일러가 타입을 더 정확하게 추론하도록 돕는 도구입니다.

as 단언의 올바른 사용법

as는 타입 간 변환이 아닙니다. 컴파일러가 타입을 알 수 없거나 잘못 추론할 때 개발자가 정확한 타입을 알려주는 것입니다.

// 파일: src/assertions/as-basic.ts// DOM 조작 — getElementById는 HTMLElement | null을 반환하지만// id가 "canvas"인 요소가 HTMLCanvasElement임을 개발자가 확신할 때const canvas = document.getElementById("canvas") as HTMLCanvasElement;const ctx = canvas.getContext("2d"); // HTMLCanvasElement의 메서드 사용 가능// JSON 파싱 — 결과가 unknown이므로 타입을 지정해야 할 때interface Config {  apiUrl: string;  timeout: number;}const rawConfig = JSON.parse(localStorage.getItem("config") ?? "{}") as Config;

as 단언은 TypeScript가 컴파일 타임에만 동작합니다. 런타임에는 아무 검증도 하지 않습니다. 따라서 값이 실제로 해당 타입인지 보장할 수 없을 때는 타입 가드를 사용해야 합니다.

as 단언의 남용 패턴과 문제점

// 파일: src/assertions/as-abuse.ts// 나쁜 예시 — any로 만들어서 모든 검사를 우회function badExample(data: unknown) {  const user = data as any; // 모든 타입 검사 무력화  console.log(user.name.toUpperCase()); // 런타임 에러 가능}// 나쁜 예시 — 관련 없는 타입으로 단언 (이중 단언)const number = 42 as unknown as string; // 컴파일은 통과하지만 의미 없음console.log(number.toUpperCase()); // 런타임 에러// 좋은 예시 — 타입 가드로 대체function goodExample(data: unknown) {  if (    typeof data === "object" &&    data !== null &&    "name" in data &&    typeof (data as { name: unknown }).name === "string"  ) {    const user = data as { name: string };    console.log(user.name.toUpperCase()); // 안전  }}

이중 단언(double assertion)인 value as unknown as OtherType은 TypeScript의 안전장치를 완전히 우회합니다. 정말 불가피한 경우에만 사용하고, 주석으로 이유를 명시해야 합니다.

as const — 리터럴 타입 고정

as const를 붙이면 값의 타입이 가능한 한 좁은 리터럴 타입으로 고정됩니다. 객체의 모든 속성이 readonly가 되고, 배열이 읽기 전용 튜플이 됩니다.

// 파일: src/assertions/as-const.ts// as const 없이const config1 = {  endpoint: "/api/users",  method: "GET",  timeout: 3000,};// config1.method의 타입: string (넓은 타입)// config1.timeout의 타입: number// as const 사용const config2 = {  endpoint: "/api/users",  method: "GET",  timeout: 3000,} as const;// config2.method의 타입: "GET" (리터럴 타입)// config2.timeout의 타입: 3000 (리터럴 타입)// 모든 속성이 readonly

as const로 enum 대체하기

enum 대신 as const 객체를 사용하는 패턴이 실전에서 많이 쓰입니다.

// 파일: src/assertions/const-enum.ts// enum 방식enum Direction {  Up = "UP",  Down = "DOWN",  Left = "LEFT",  Right = "RIGHT",}// as const 방식 (권장)const Direction = {  Up: "UP",  Down: "DOWN",  Left: "LEFT",  Right: "RIGHT",} as const;// as const 객체에서 값 타입 추출type Direction = (typeof Direction)[keyof typeof Direction];// "UP" | "DOWN" | "LEFT" | "RIGHT"function move(direction: Direction) {  console.log(`이동: ${direction}`);}move(Direction.Up);   // 정상move("UP");           // 정상 — 리터럴 타입도 허용// move("up");        // 오류 — 소문자는 타입 불일치

as const 방식은 enum과 달리 컴파일 후 객체 그대로 남아 있어 tree-shaking이 가능하고, 일반 JavaScript 객체처럼 사용할 수 있습니다.

배열에 as const 적용하기

// 파일: src/assertions/const-array.ts// as const 없이 — string[]const permissions1 = ["read", "write", "delete"];// permissions1의 타입: string[]// as const — readonly 튜플const permissions2 = ["read", "write", "delete"] as const;// permissions2의 타입: readonly ["read", "write", "delete"]type Permission = (typeof permissions2)[number];// "read" | "write" | "delete"function hasPermission(user: { permissions: Permission[] }, action: Permission): boolean {  return user.permissions.includes(action);}// 설정 배열을 타입으로 활용하는 패턴const ROUTES = [  { path: "/", name: "home" },  { path: "/about", name: "about" },  { path: "/users", name: "users" },] as const;type RouteName = (typeof ROUTES)[number]["name"];// "home" | "about" | "users"

non-null 단언 연산자 !

!는 값이 null 또는 undefined가 아님을 단언하는 특수 연산자입니다.

// 파일: src/assertions/non-null.tsconst input = document.getElementById("username") as HTMLInputElement;// ! 없이if (input.value !== null) {  console.log(input.value.trim());}// ! 사용 — null이 아님을 확신할 때console.log(input!.value.trim());// 속성 접근 시에도 사용 가능interface Config {  database?: {    host: string;  };}const config: Config = { database: { host: "localhost" } };console.log(config.database!.host); // database가 항상 있다고 확신할 때

non-null 단언도 런타임 검사를 하지 않습니다. 실제로 null일 경우 TypeError가 발생합니다. 옵셔널 체이닝(?.)이나 null 검사를 먼저 고려하고, 그래도 단언이 필요할 때만 사용합니다.

단언 사용 결정 기준

상황 권장 방법
런타임에 실제로 타입을 확인해야 함 타입 가드 (is, instanceof, typeof)
컴파일러보다 확실히 더 많이 알 때 as 단언
null/undefined 아님을 확신할 때 ! 또는 as T (단, 주의해서 사용)
값이 변하지 않을 상수 as const
유니온에서 특정 타입 강제 as 단언 (타입이 서로 겹칠 때만)

타입 단언은 TypeScript와의 협력이 아닌 명령입니다. as const는 협력입니다. 가능하면 as const로 정확한 타입을 확보하고, as 단언은 불가피한 경우에만 최소한으로 사용하는 것이 좋습니다.