iBetter Books
수정

세 구역 이야기

어떤 건물에 세 구역이 있다고 상상해보세요.

첫 번째 구역은 무법지대입니다. 신분증 확인 없이 누구나 들어올 수 있고, 무엇이든 할 수 있습니다. 편리하지만 위험합니다. 이것이 any입니다.

두 번째 구역은 신원확인 필수 구역입니다. 들어올 수는 있지만, 뭔가를 하려면 신분 확인을 먼저 해야 합니다. 번거롭지만 안전합니다. 이것이 unknown입니다.

세 번째 구역은 존재할 수 없는 곳입니다. 지도에는 표시되어 있지만 실제로는 아무도 도달할 수 없습니다. 이것이 never입니다.

any — 무법지대

any는 TypeScript의 타입 검사를 완전히 끄는 타입입니다.

let data: any = "문자열";data = 42;         // OKdata = true;       // OKdata = { x: 1 };  // OKdata = [1, 2, 3];  // OK

어떤 값이든 담을 수 있습니다. 문제는 이게 끝이 아닙니다.

let data: any = "김민수";const upper = data.toUpperCase();  // OK — 하지만 런타임에서 확인해야 함const num = data * 2;              // OK — TypeScript는 에러 안 냄data.nonExistentMethod();          // OK — TypeScript는 에러 안 냄

any 타입에는 아무 메서드나 호출할 수 있고, 어떤 연산이든 할 수 있습니다. TypeScript가 검사를 포기했기 때문입니다. 그러면 에러는 코드를 실행할 때 납니다. TypeScript를 쓰는 이유가 사라집니다.

any가 퍼지면 더 무서워집니다.

let data: any = "김민수";const result = data * 2; // result의 타입도 any가 됨const final = result + 1; // final의 타입도 any가 됨

any에서 나온 값도 any가 됩니다. 한 곳에서 시작된 any가 코드 전체로 퍼져나갑니다.

언제 any를 써도 될까요. 서드파티 라이브러리가 타입을 제공하지 않거나, 복잡한 타입 문제를 일시적으로 넘어가야 할 때입니다. 하지만 임시방편으로 사용하고, 가능한 빨리 구체적인 타입으로 교체해야 합니다.

unknown — 신원확인 필수 구역

unknownany처럼 어떤 값이든 담을 수 있지만, 사용하기 전에 타입을 확인해야 합니다.

let data: unknown = "김민수";// 타입 확인 없이 사용하면 에러const upper = data.toUpperCase(); // 에러! Object is of type 'unknown'.const num = data * 2;             // 에러!

any와 달리 unknown은 아무 작업도 허용하지 않습니다. 반드시 타입을 좁혀야 합니다.

let data: unknown = "김민수";// 타입 확인 후 사용if (typeof data === "string") {  const upper = data.toUpperCase(); // OK — 이 블록 안에서 data는 string}if (typeof data === "number") {  const doubled = data * 2; // OK — 이 블록 안에서 data는 number}

typeof로 타입을 확인하면, 해당 블록 안에서는 TypeScript가 구체적인 타입으로 인식합니다. 이것을 타입 좁히기(Type Narrowing)라고 합니다. 자세한 내용은 PART 05에서 다룹니다.

unknown은 외부에서 들어오는 데이터를 다룰 때 유용합니다. API 응답, JSON.parse() 결과, 에러 객체 등이 대표적입니다.

try {  // 무언가를 시도} catch (error: unknown) {  if (error instanceof Error) {    console.log(error.message); // OK  }}

에러 객체는 어떤 타입인지 모르므로 unknown으로 처리한 뒤, 타입을 확인하고 사용하는 것이 안전합니다.

never — 존재할 수 없는 곳

never는 "절대 발생할 수 없는 타입"입니다. 값이 존재하지 않는 상태를 표현합니다.

직접 쓰기보다 자연스럽게 나오는 경우가 많습니다.

모든 경우를 처리한 뒤 남은 타입

type Shape = "circle" | "square" | "triangle";function getArea(shape: Shape): number {  if (shape === "circle") {    return Math.PI * 5 * 5;  } else if (shape === "square") {    return 5 * 5;  } else if (shape === "triangle") {    return (5 * 5) / 2;  } else {    // 여기서 shape의 타입은 never    // 모든 경우를 처리했으므로 이 코드에 도달할 수 없음    const exhausted: never = shape;    throw new Error(`처리되지 않은 도형: ${exhausted}`);  }}

Shape의 모든 경우를 처리했으므로 else 블록에서 shapenever가 됩니다. 나중에 Shape에 새로운 도형을 추가하면 이 패턴이 에러를 내주어 처리를 빠트리지 않도록 도와줍니다.

절대 반환하지 않는 함수

function throwError(message: string): never {  throw new Error(message);}function infiniteLoop(): never {  while (true) {}}

함수가 항상 예외를 던지거나 무한 루프라면 반환값이 없습니다. 이때 반환 타입이 never입니다. void와 다릅니다. void는 "값 없이 반환한다"이고, never는 "반환 자체가 일어나지 않는다"입니다.

세 타입 비교표

타입 담을 수 있는 값 사용 전 확인 필요 사용 예
any 모든 값 필요 없음 (위험) 임시 회피, 레거시 코드
unknown 모든 값 반드시 필요 API 응답, JSON 파싱, 에러 처리
never 없음 해당 없음 철저한 분기 처리, 무한 루프, 예외 던지기

any 대신 unknown을 써야 하는 이유

// any — 위험function processAny(data: any) {  return data.toUpperCase(); // 런타임에 에러날 수 있음}// unknown — 안전function processUnknown(data: unknown) {  if (typeof data === "string") {    return data.toUpperCase(); // 안전하게 처리  }  return "";}

타입을 모르는 값이 들어올 때 any를 쓰면 TypeScript의 보호막이 사라집니다. unknown을 쓰면 타입 확인을 강제하므로, 잘못된 접근을 컴파일 시점에 잡아줍니다.

다음 챕터에서는 TypeScript 코드를 작성하다 처음 만나는 에러 메시지들을 어떻게 읽는지, 당황하지 않는 법을 살펴보겠습니다.

Ch 05. 정체를 알 수 없는 값 — any, unknown, never — 소설처럼 읽는 TypeScript | iBetter Books