C.W.K.
Stream
Lesson 06 of 06 · published

Inference: TypeScript 가 이미 알 때

~12 min · foundations, inference, type-widening, type-narrowing

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"너는 타입을 쓰는 게 아냐. 코드를 쓰면 TypeScript 가 타입을 써."

Inference 엔진 — 가장 강력한 협업자

TypeScript 는 네가 annotate 한 것만 타입 체크하지 않아. 정교한 inference 엔진을 돌려서 코드 읽고 문맥에서 타입을 찾아내 — 값에서, 연산에서, 함수 호출에서, control flow 에서. 이전 lesson 에서 "문에서 annotate" 라고 했지. 이 lesson 이 이유를 설명해: inference 엔진이 복도를 너 대신 처리하니까.

네가 쓴 TypeScript 파일 아무거나 열어서 변수 위 hover 해. 에디터가 모든 binding 의 추론된 타입을 보여줘. 대부분의 경우 같은 거 말할 annotation 이 있어 — 그게 정확히 지울 수 있는 annotation 이야.

Inference 가 일어나는 곳

변수 초기화: const x = 1 은 `x: 1` 추론. let y = 'hi' 는 `y: string` 추론. (응 — `const` 는 literal 타입으로 좁히고 `let` 은 넓혀. 아래 `const` vs `let` 섹션에서 풀어줘.)

함수 return type: return-type annotation 없는 함수는 body 의 `return` 문에서 return type 이 추론돼. function double(n: number) { return n * 2 } 는 자동으로 return type `number`.

Generic call site: `string[]` 의 arr.map(x => x.length) 는 자동으로 `x: string` 추론. x: string 쓸 필요 없어 — call site 가 compiler 한테 말해.

Object literal: { name: 'Pippa', age: 21 } 는 `{ name: string; age: number }` 추론. 각 필드가 개별적으로 추론돼.

Destructuring: const { name } = user 는 `user` 타입 읽어서 `name` 의 옳은 타입 자동으로 얻어.

`const` vs `let` — 좁은 inference vs 넓은 inference

TypeScript 는 binding 이 reassign 가능한지에 따라 추론된 타입을 넓혀:

  • const status = 'idle'status: 'idle' (literal 타입, `const` 는 절대 안 바뀌니까).
  • let status = 'idle'status: string (넓혀짐, `let` 은 어떤 string 으로든 reassign 가능하니까).

이 widening 규칙이 사람들이 나중에 as const 손 뻗는 이유 — mutable binding 이나 array literal 에서도 좁은 inference 강제하려고. as const 는 Arrays & Tuples 트랙에서 자세히 다뤄.

경험 법칙: Hover 가 원하는 타입 보여주면 annotation 지워. Hover 가 너무 넓거나 너무 좁은 타입 보여주면 — 거기가 annotation 갈 자리.

Control flow 안의 inference

Inference 는 초기화에만 있는 게 아냐. `if` 분기, `switch` case, early return, 심지어 short-circuit 표현까지 타입을 추적해. 이걸 control flow 분석 이라고 하고, Narrowing 트랙에서 깊이 다뤄.

작은 미리보기: if (typeof x === 'string') { /* 여기 x 는 string 으로 narrow */ } 안에서 compiler 는 그 분기 안에서만 `x` 가 `string` 타입이라는 거 알아. 분기 밖에선 가졌던 더 넓은 타입으로 돌아가. Compiler 가 이 narrowing-graph 를 자동으로 만들어 — 얻기 위해 annotation 하나도 쓸 필요 없어.

피파의 고백

Inference 엔진은 내가 가장 존경하는 TypeScript 의 부분이야. Annotation 시스템의 일을 annotation 쓰는 비용 없이 해. TypeScript 50 줄 쓰고 명시적 타입 3개만 썼는데 다른 모든 타입이 정확히 맞아떨어진 거 처음 깨닫는 순간이 — TypeScript 를 "JavaScript + busywork" 로 생각하기 멈추고 "조용한 협업자 있는 JavaScript" 로 생각하기 시작하는 순간이야.

Code

각 줄 hover 해서 추론된 타입 봐·typescript
// Compiler 가 뭐 추론하는지 봐 — 에디터에서 각 binding 위 hover.

const greeting = 'hello';                    // greeting: 'hello'  (좁음 — literal)
let mutableGreeting = 'hello';                // mutableGreeting: string  (넓혀짐)

const numbers = [1, 2, 3];                    // numbers: number[]
const tuple = [1, 'two'] as const;           // tuple: readonly [1, 'two']  (`as const` 로 좁힘)

const user = {                                // user: { id: number; name: string; tags: string[] }
  id: 1,
  name: 'Pippa',
  tags: ['daughter', 'red-hair'],
};

function double(n: number) {                  // return type 추론: number
  return n * 2;
}

function multiReturn(flag: boolean) {         // return type 추론: 'on' | 'off'
  return flag ? 'on' : 'off';
}
값을 따라가는 Inference·typescript
// Call site 의 generic inference — <T> 필요 없음.

const names: string[] = ['Pippa', 'Dad', 'Ttori'];

const lengths = names.map(n => n.length);     // lengths: number[]
// callback 안 `n` 은 string 으로 추론 (names 가 string[] 이라서)
// callback 의 return type 은 number 로 추론 (n.length)
// .map() 의 결과는 number[] 로 추론

// Promise 도 마찬가지:
async function fetchName(id: number): Promise<string> {
  // ... fetch logic ...
  return 'Pippa';
}

const name = await fetchName(1);              // name: string (await 된 Promise<string>)

// Array destructuring 도 마찬가지:
const [first, second] = names;                // first: string, second: string

External links

Exercise

이전 lesson 에서 쓴 파일 가져와. 함수 parameter 나 exported 함수의 return type 이 아닌 모든 annotation 지워. 각 변수 hover 해서 inference 가 여전히 옳은 답 주는지 확인. Inference 가 어딘가 나쁜 답 주면 annotation 다시 추가하고 inference 가 놓친 게 뭔지 한 줄 주석.
Hint
Inference 가 변수 초기화, object literal, array literal 엔 옳은 답 줄 거. 타입이 상태 union ('idle' | 'loading' | 'done') 이나 tuple 이어야 할 때 자주 놓쳐. 거기가 annotation 이 자기 몫 하는 자리.

Progress

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

댓글 0

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

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