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

Contextual Typing: TypeScript 가 너의 Callsite 읽을 때

~10 min · functions, contextual-typing, inference, callbacks

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"호출 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.

규칙: 함수가 들어가는 slot 이 이미 함수 타입 가지면, parameter 다시 annotate 안 해. Inference 가 채우게 둬. 예외는 inference 가 너무 넓거나 너무 좁은 거 고를 때 — 거기서 annotation 추가해서 override.

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

피파의 고백

cwkPippa frontend 에 수천 개 arrow callback 있고, 거의 어느 것도 parameter annotate 안 해. Compiler 가 배열, React event handler, hook 의 setter, Promise 의 `.then` 읽고 — 모든 callback parameter 자동으로 타입 붙임. 결과는 type system 이 여전히 이해하는 읽기 쉬운 코드. 초보자가 필요 없는 callback parameter annotate 할 때마다, 부드럽게 지워. Inference 가 너보다 이걸 더 잘 해.

Code

Contextual typing — param 이 slot 에서 와·typescript
// Contextual typing in action.

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

// 세 callback 다 parameter 자동 타입 붙음:
const upper = names.map((n) => n.toUpperCase());      // n: string
const long = names.filter((n) => n.length > 3);       // n: string
const sumChars = names.reduce((acc, n) => acc + n.length, 0);  // acc: number, n: string

// React-style event handler — 같은 아이디어.
document.body.addEventListener('click', (e) => {
  // e: MouseEvent — addEventListener 의 signature 에서
  e.preventDefault();
});

// 함수 타입 가진 변수 — alias 에서 param.
type Reducer<T> = (acc: T, x: T) => T;
const sumPair: Reducer<number> = (a, b) => a + b;     // a: number, b: number
Contextual typing 실패할 때 — 그리고 복구 법·typescript
// 실패할 때 — 그리고 해결.

// 실패: context 없음, parameter implicit any.
const broken = (x) => x + 1;          // ❌ Parameter 'x' implicitly has 'any' type

// Fix 1: parameter annotate.
const withAnnotation = (x: number) => x + 1;

// Fix 2: 변수 annotate.
const withVarType: (x: number) => number = (x) => x + 1;

// Fix 3: 타입 붙은 위치에서 사용.
const nums = [1, 2, 3];
const doubled = nums.map((x) => x * 2);  // x 가 배열에서 타입 — annotation 불필요

// Over-annotate 유혹 피해.
const overkill = nums.map((x: number, i: number) => x + i);  // param 이미 알려짐; annotation 은 noise

External links

Exercise

Codebase 나 library 에서 3개 다른 callback API 들고 와: Array.map, addEventListener, Promise.then. 각각에 parameter 의미 있게 쓰는 inline callback 써. 어느 parameter 도 annotate 마. Hover 로 TypeScript 가 타입 아는지 확인. 그다음 그 callback 중 하나를 named 함수로 빼 — 뭐 바뀌어?
Hint
Inline: contextual typing 적용, annotation 불필요. Named-and-then-passed: named 함수가 독립적으로 annotate 돼야 함, 정의 site 에 읽을 context 없으니까.

Progress

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

댓글 0

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

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