"문에 annotate 하고, 복도엔 안 해."
Annotation 5개 위치
모든 TypeScript annotation 은 5개 자리 중 하나에 살아. 5개 다 이름 댈 수 있으면 문법 생각 안 하고 TS 쓸 수 있어 — 두뇌가 콜론이 어디 가는지 완전한 지도를 가졌으니까.
- 변수 annotation:
const x: number = 1. 콜론이 타입을 binding 이름에 묶어. - Parameter annotation:
function f(a: string, b: number). 콜론이 타입을 각 parameter 에 묶어. - Return type annotation:
function f(): string. 콜론이 함수 전체에 묶이고 return 가능한 거 제한해. - Property annotation (type/interface 선언 안):
{ name: string; age: number }. Object 모양의 각 member 가 콜론 받아. - Type parameter constraint:
function f<T extends string>(x: T). Generic 은 `:` 대신 `extends` 써, constraint 가 subtype 관계니까.
그게 다야. 5개 자리. TypeScript annotation 문법의 나머지는 다 이 5개 중 하나의 variation 이야.
문 규칙
초보자는 over-annotate 해. Compiler 가 이미 아는 변수까지 포함해서 모든 변수에 타입 붙여. 그러면 코드가 noisy 해지고, inference 엔진과 싸우고, 타입 바뀔 때 유지보수 부담이 생겨.
프로의 수: 문에서 annotate: 함수 parameter (들어가는 문), 함수 return type (나가는 문), exported public API surface (모듈 사이의 문). 함수 body 안에선 compiler 가 inference 하게 둬. 함수 call site 안에서도 compiler 가 inference 하게 둬. 문은 contract 쓰는 곳, 복도는 compiler 가 너 대신 contract 돌리는 곳.
let x = 1 은 `x: number` 추론. const arr = [1, 2, 3] 은 `arr: number[]` 추론. const obj = { name: 'Pippa' } 는 `obj: { name: string }` 추론. 이 줄들에 : number, : number[], : { name: string } 쓰는 건 안전 안 더하고 noise 만 더해. Compiler 가 이미 알았어. (const x = 1 로 바꾸면 inference 가 literal 타입 `1` 로 좁혀져 — primitive 에 대한 `const` 의 widening 규칙. 다음 lesson 에서 다뤄.)Inference 가 작동해도 annotate 할 때
문 규칙의 3가지 예외:
1. Inference 가 너무 좁은 타입 고를 때. const status = 'idle' 은 `status: 'idle'` (literal 타입) 추론. "여러 상태 중 하나" 의도면 넓혀야 돼: const status: 'idle' | 'loading' | 'done' = 'idle'.
2. Inference 가 너무 넓은 타입 고를 때. const colors = ['red', 'blue'] 는 `colors: string[]` 추론. Tuple 원하면 annotate: const colors: ['red', 'blue'] = ['red', 'blue'] — 또는 as const 써.
3. 타입이 문서 역할 할 때. Exported 함수의 명시적 return type 이 caller 가 읽을 contract 일 때 있어. Inference 가 같은 답 줘도, annotation 이 의도를 신호해.