"호출 site 가 이미 parameter 의미 선언했을 때 TypeScript 한테 말해줄 필요 없어."
Contextual typing 이 뭐
함수가 둘러싼 코드가 이미 함수 타입 선언한 위치에 있을 때 — 예: arr.map(...) 의 callback parameter 가 (x: T, i: number) => U 로 알려져 있을 때 — TypeScript 가 그 선언 써서 함수 parameter 를 자동으로 타입 붙임. (x) annotate 할 필요 없어; TypeScript 가 context 에서 x 가 뭔지 이미 알아.
이게 arr.map(x => x.length) 를 x 가 string 이라고 명시 안 하고 쓸 수 있는 이유. Compiler 가 arr 의 타입 (예: string[]) 읽고, .map 의 signature 찾고, callback 이 string 받는 거 보고, x 를 그렇게 타입 붙여. Arrow 함수가 자기가 채울 slot 에서 parameter 타입 상속.
Contextual typing 이 일어나는 곳
- 함수 argument:
arr.filter(x => x > 0)—x가 배열 element 타입에서 타입 붙음. - Object property assign 을 타입 붙은 slot 에:
const cb: (x: number) => void = (x) => ...—x가number. - 타입 붙은 변수 선언의 오른쪽:
const f: Reducer<User> = (acc, u) => ...— 둘 다 alias 에서 타입. - Return 위치 함수 모양: 함수가 다른 함수 return 하고 outer return type 선언됐을 때, inner 함수의 param 이 타입.
왜 중요
Contextual typing 이 TypeScript callback 의 ergonomic 을 자연스럽게 느끼게 만드는 거. 없으면 모든 callback parameter 를 annotate 해야 함 — arr.map((x: string) => x.toUpperCase()) — 반복적이고 깨지기 쉬워 (배열의 element 타입 rename 하면 모든 callback update 해야 함). 있으면 callback 이 전달되는 API 에서 parameter 타입 채택. 결과는 간결하고, 옳고, refactor-friendly.
Contextual typing 실패할 때
Contextual typing 은 둘러싼 context 가 이미 함수 타입 선언했을 것 요구. Annotation 없는 let binding 의 standalone arrow 함수는 parameter 타입 추론 못 함 — 추론 할 게 아무것도 없으니까. Fix 는 변수 annotate (또는 타입 붙은 위치에서 사용).
const f = (x) => x + 1; // ❌ Parameter 'x' implicitly has 'any' type
const g: (x: number) => number = (x) => x + 1; // ✅ 변수의 타입에서 contextual
[1, 2].map((x) => x + 1); // ✅ 배열에서 contextual
피파의 고백
.then 읽고 — 모든 callback parameter 자동으로 타입 붙임. 결과는 type system 이 여전히 이해하는 읽기 쉬운 코드. 초보자가 필요 없는 callback parameter annotate 할 때마다, 부드럽게 지워. Inference 가 너보다 이걸 더 잘 해.