C.W.K.
Stream
Lesson 03 of 05 · published

`any`, `unknown`, `never`: Type-System 의 극단

~12 min · primitives, any, unknown, never, top-type, bottom-type

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"any 는 off switch. unknown 은 조심 switch. never 는 불가능."

다른 것들처럼 행동 안 하는 3개 타입

대부분 타입은 값이 뭐냐 에 대한 거. any, unknown, nevertype system 자체가 뭐 하냐 에 대한 거. Meta-tool 이야 — escape hatch, 안전한 escape hatch, 불가능 표시 — 잘 배우면 초보 TypeScript 와 프로 TypeScript 를 분리해.

any — off switch

any 는 annotate 한 값에 대해 type system 끄. Property 접근 체크 안 함. Method 호출 체크 안 함. 다른 타입에 assignment 체크 안 함. Loose 함이 전염 — (anyValue).foo.bar.baz 다 `any` 유지, 그리고 접근 subtree 전체에 대해 타입 안전 잃어.

any 의 정당한 사용은 드물어: 타입 없는 JavaScript library 와의 interop (그래도 `.d.ts` 선언 쓰는 게 나음), 일회성 디버깅, legacy 코드 migration. Strict codebase 에선 모든 `any` 가 lint 규칙으로 flag 되어야 하고 정당화 주석 필요.

unknown — 조심 switch

unknown 은 어떤 값이든 받아 (any 처럼) 근데 narrow 하기 전엔 아무것도 못 하게 해. Method 호출 못 함, property 접근 못 함, typed 변수에 assign 못 함. typeof / instanceof / 커스텀 type guard 먼저 써야 함.

슬로건: unknown 이 처음부터 any 가 됐어야 하는 거. 들어갈 때 같은 유연성, 나갈 때 훨씬 더 많은 안전. Strict Mode 트랙의 useUnknownInCatchVariables flag 가 catch (e) 를 default 로 unknown 하게 만들어 — 정확히 이 안전이 너의 에러 처리에 도달하도록.

never — 불가능

never 는 empty 타입. 어떤 값도 assignable 안 함. 사용처는 매우 구체적이고 매우 유용:

  • Throw 하거나 영원히 loop 하는 함수의 return type — "이 함수는 정상으로 return 안 함."
  • Exhaustive narrowing 의 else 분기 — "이 분기는 도달 불가능, 증명해."
  • "이 case 적용 안 됨" 의미하는 conditional 타입의 결과 — Type Manipulation 트랙에서 봐.

변수가 예상 안 했는데 never 타입 받으면, compiler 가 type-flow 가 불가능 상태 도달했다고 말하는 거. Signal, target 아냐.

위계: any 는 타입 격자 밖에 앉아 — compiler 가 체크 거부하는 top + bottom 둘 다. unknown 은 진짜 top 타입 (모든 게 assignable). neverbottom 타입 (아무것도 assignable 안 함, 그리고 모든 것에 assignable). 격자 안 위치로 보면 행동이 predictable 해져.

피파의 고백

cwkPippa 코드 읽다가 : any 발견하면 첫 반응 "왜?", 두 번째 반응 "이거 `unknown` 으로 바꿀 수 있어?". 30,000 줄 cwkPippa frontend 의 정당한 `any` 수는 한 자릿수 — 그리고 각각 narrowing 이 가능하지 않았던 이유 설명하는 주석 있어. never 는 exhaustive switch 와 conditional-type return 에 나타나. 둘 다 의도적. unknown 은 내가 완전히 제어 안 하는 어떤 경계든 default — 들어오는 JSON, 3rd-party event, validate 전 parse 하는 어떤 거든.

Code

any 는 다 통과시켜. unknown 은 narrow 강제.·typescript
// any — off switch (이거 손 안 뻗어).
let danger: any = 'hello';
danger = 42;                  // ✅ any 가 뭐든 받음
danger = { foo: { bar: 1 } };
danger.foo.bar.baz.qux();     // ✅ 컴파일 — 그리고 runtime 에 크래시
                              // Loose 함이 모든 접근 통해 퍼짐.

// unknown — 조심 switch.
let careful: unknown = 'hello';
careful = 42;                 // ✅ assignment OK
careful = { foo: 1 };         // ✅ assignment OK

careful.toUpperCase();        // ❌ Object is of type 'unknown'
careful.foo;                  // ❌ Object is of type 'unknown'

// 먼저 narrow 해야 함:
if (typeof careful === 'string') {
  careful.toUpperCase();      // ✅ string 으로 narrow
}
Bottom 타입으로서의 never — 3개의 canonical 사용·typescript
// never — 불가능.

// 1. 정상으로 return 안 하는 함수의 return type.
function throwIt(msg: string): never {
  throw new Error(msg);
}
function loopForever(): never {
  while (true) { /* ... */ }
}

// 2. Exhaustive switch 의 else 분기.
type Shape = { kind: 'circle'; r: number } | { kind: 'square'; s: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle': return Math.PI * shape.r ** 2;
    case 'square': return shape.s ** 2;
    default:
      // Shape 에 새 variant 추가하고 case 잊으면,
      // 이 assignment 가 compile 시점에 실패:
      const _exhaustive: never = shape;
      return _exhaustive;
  }
}

// 3. 위계 in action.
const neverVal!: never = undefined as never;
const asString: string = neverVal;  // ✅ never 는 어디든 assignable
const asNumber: number = neverVal;  // ✅ 같음
// 아무것도 never 안으로 assignable 안 됨 — 그래서 bottom.

External links

Exercise

JSON.parse 쓰는 parseJson(input: string): unknown 함수 써. Return type 이 any 아니라 unknown 인 거 주목 — JSON.parseany return 하는 standard-library 함수 중 하나고, 많은 스타일 가이드가 감싸라고 권해. 그다음 parseJson 호출하고 필수 필드 체크해서 결과 narrow 하는 parseUser(input: string): User | null 써. unknown 이 어떻게 any 가 스킵하게 해줬을 validation 을 쓰라고 강제하는지 봐.
Hint
const parsed = parseJson(input) 후엔 아직 parsed.name 못 써. User 를 return 할 수 있기 전 if 체크 연속 (typeof parsed === 'object', parsed !== null, 'name' in parsed, typeof parsed.name === 'string') 써야 함. 그게 안전의 대가 — 그리고 그건 지불할 가치 있어.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고
💛 by 똘이warm

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.