본문 바로가기
C.W.K.
Stream
Lesson 05 of 05 · published

문맥적 타이핑: 함수가 들어갈 자리가 타입을 알려 줄 때

~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
"호출 자리가 이미 매개변수의 뜻을 알고 있다면, 콜백이 같은 말을 다시 적을 필요는 없어."

바깥 계약이 안쪽 함수로 흐른다

문맥적 타이핑은 함수가 들어갈 자리의 타입을 이용해 매개변수와 반환값을 추론하는 과정이야. 문자열 배열의 map에 넣은 (item) => item.length에서 item을 따로 string이라고 적지 않아도 돼. 배열 메서드의 시그니처가 콜백이 받을 원소 타입을 이미 알고 있기 때문이야.

타입이 붙은 변수에 화살표 함수를 대입할 때, 객체의 함수 속성을 채울 때, 이벤트 처리기를 JSX에 바로 적을 때도 같은 방향으로 정보가 흐른다. 함수가 자기 타입을 밖으로 밀어내는 것이 아니라, 자기가 들어갈 칸의 계약을 안으로 받아오는 거야.

왜 콜백 코드가 짧아지는가

모든 콜백 매개변수에 타입을 반복하면 원본 타입이 바뀔 때 여러 자리를 함께 고쳐야 하고, 사람이 더 넓거나 잘못된 타입을 적을 수도 있어. 문맥적 타이핑은 API가 한 번 선언한 계약을 모든 호출에 재사용해. 짧아지는 것은 보너스고 진실의 원천이 하나가 되는 것이 핵심이야.

독립 함수로 꺼내면 문맥이 끊길 수 있어

items.map((x) => x.id)에서는 x의 타입을 알지만, function getId(x) { ... }를 따로 선언한 뒤 map에 넘기면 선언 시점에는 x를 추론할 문맥이 없어. 그 함수의 매개변수에 타입을 붙이거나 함수 변수 전체에 시그니처를 줘야 해.

리팩터링으로 인라인 콜백을 이름 있는 함수로 빼냈더니 암묵적 any 오류가 생기는 이유가 바로 이거야. 컴파일러가 갑자기 둔해진 게 아니라 정보가 흐르던 연결을 끊은 거지.

추론과 명시의 균형

문맥이 정확하다면 중복 표기를 지워. 문맥이 너무 넓거나 라이브러리 타입이 any라면 경계에 타입을 보충해. 콜백마다 억지로 적기보다 먼저 바깥 API의 시그니처가 정확한지 확인하는 편이 근본적인 해결이야.

콜백 타입이 흐려지면 콜백보다 먼저 그 콜백을 받는 자리를 봐. 좋은 API 타입 하나가 수백 개의 호출을 정확하게 만든다.

피파의 고백

프론트엔드의 수많은 이벤트 처리기와 배열 콜백에 매개변수 타입을 거의 적지 않아도 되는 이유가 이 기능이야. 추론을 믿는다는 건 아무것도 안 하는 게 아니라, 타입 정보가 어디서 흘러오는지 알고 가장 좋은 원천에 투자하는 일이야.

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

Array.map, addEventListener, Promise.then에 인라인 콜백을 넣고 매개변수 추론을 확인해. 그중 하나를 이름 있는 독립 함수로 빼서 어떤 타입 정보가 사라지는지 비교해.
Hint
인라인 콜백은 호출 자리에서 타입을 얻어 별도 주석이 필요 없어. 독립 함수는 정의 지점에 문맥이 없으므로 매개변수 타입을 스스로 선언해야 해.

Progress

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

댓글 0

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

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