~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
"추론은 타입을 생략하는 요령이 아니라, 값에서 나온 증거를 끝까지 보존하는 기능이야."
값이 타입을 말해 줘
const n = 3을 보면 TypeScript는 n이 숫자라는 사실을 알아. 문자열 배열을 만들면 원소가 string이라고 추론하고, 함수가 항상 숫자를 반환하면 반환 타입도 number라고 계산해. 이처럼 코드에 이미 있는 증거에서 타입을 얻는 과정이 추론이야.
추론을 믿으면 타입 표기가 줄어드는 것보다 더 큰 이득이 생겨. 값을 바꾸면 타입도 함께 바뀌고, 그 값을 쓰는 곳의 오류가 자동으로 따라와. 사람이 여러 자리에 복사한 표기보다 한 진실에서 파생된 타입이 변경에 강해.
문맥도 반대 방향으로 흐른다
타입은 값에서 바깥으로만 퍼지지 않아. 콜백이 들어갈 자리가 (item: User) => string이라고 정해져 있으면, item을 따로 적지 않아도 User로 추론돼. JSX 이벤트 처리기나 배열의 map 콜백이 자연스럽게 타입을 얻는 이유가 이 문맥적 타이핑이야.
함수를 변수로 빼내는 순간 문맥이 사라질 수 있다는 점은 조심해. 인라인 콜백에서는 알던 매개변수 타입을 독립 함수 선언에서는 모를 수 있어. 그때는 함수 경계에 타입을 다시 적어 주면 돼.
추론이 너무 넓거나 너무 좁을 때
let status = 'ready'는 이후 다른 문자열이 들어올 수 있어 보통 string으로 넓어져. 반대로 useState(null)은 초깃값만 보면 null밖에 알 수 없어 나중에 User가 들어올 의도를 놓쳐. 이런 자리에서는 'ready' | 'done'이나 User | null처럼 사람이 알고 있는 더 넓은 계약을 명시해야 해.
좋은 판단 기준은 간단해. 현재 값이 앞으로 가능한 모든 값을 대표하면 추론을 믿고, 초깃값이 전체 상태의 일부일 뿐이면 타입을 보충해. 컴파일러와 사람이 가진 정보가 다른 지점을 찾는 거야.
반환 타입을 적을 때와 맡길 때
작은 내부 함수는 반환 타입 추론이 구현과 함께 움직여 편리해. 공개 API나 재귀 함수, 여러 분기가 복잡한 함수는 반환 타입을 적어 계약을 먼저 고정하는 편이 좋아. 구현이 우연히 undefined를 흘리거나 너무 넓은 유니언을 만들면 함수 안에서 바로 오류가 나기 때문이야.
추론을 기본으로, 의도를 예외로 적어. 컴파일러가 증거로 알 수 있는 사실은 맡기고, 미래 상태나 공개 계약처럼 코드 한 줄만으로 알 수 없는 의도는 사람이 밝혀 줘.
피파의 고백
TypeScript를 오래 쓸수록 타입을 덜 적게 됐어. 느슨해진 게 아니라 컴파일러가 어디까지 정확히 아는지 알게 된 거야. 타입 표기를 줄이면서도 오류는 더 빨리 잡히는 순간, 추론이 편의 기능이 아니라 설계 도구라는 뜻이 보이기 시작해.
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