TypeScript를 처음 접하면 모든 곳에 타입을 붙여야 한다고 생각하기 쉽습니다. 하지만 TypeScript는 많은 경우 스스로 타입을 알아냅니다. 타입을 직접 명시하는 어노테이션과 TypeScript가 스스로 파악하는 추론, 두 기능을 적절히 활용하면 코드가 간결해집니다.
변수, 함수 매개변수, 반환값 등에 : 뒤에 타입을 명시하는 문법입니다.
// 변수
let name: string = "철수";
let age: number = 30;
let isActive: boolean = true;
// 함수 매개변수와 반환 타입
function add(a: number, b: number): number {
return a + b;
}
// 화살표 함수
const greet = (name: string): string => {
return `안녕하세요, ${name}`;
};
TypeScript 컴파일러는 값을 보고 스스로 타입을 결정합니다. 명시적 어노테이션 없이도 타입이 지정됩니다.
// 타입을 명시하지 않아도 string으로 추론
let name = "철수";
name = 42; // 오류: 'number'는 'string'에 할당할 수 없습니다
// 배열의 타입 추론
const scores = [95, 87, 72]; // number[]로 추론
// 함수 반환값 추론
function multiply(a: number, b: number) {
return a * b; // 반환 타입 number로 추론
}
TypeScript는 초기값, 함수 매개변수의 기본값, 이전 코드의 문맥 등을 종합해 타입을 추론합니다.
추론만으로는 부족한 경우에 어노테이션을 붙입니다.
매개변수는 항상 어노테이션이 필요합니다. 어떤 값이 들어올지 TypeScript가 추론할 수 없기 때문입니다.
// 매개변수 어노테이션 없음 → 'a', 'b'가 any로 처리됨 (strict 모드에서는 오류)
function add(a, b) {
return a + b;
}
// 어노테이션 추가
function add(a: number, b: number) {
return a + b; // 반환 타입은 number로 자동 추론
}
선언과 초기화가 분리되면 타입을 추론할 수 없습니다.
// 초기값 없음 → any로 추론됨
let result;
// 어노테이션으로 타입 지정
let result: string;
result = "완료";
result = 42; // 오류
반환값이나 변수가 복잡한 객체일 때 의도를 명확히 드러냅니다.
// 반환 타입을 명시해 계약을 분명히 함
function getUser(): { name: string; age: number } {
return { name: "철수", age: 30 };
}
추론이 충분히 동작하는 곳에 어노테이션을 붙이면 오히려 코드가 장황해집니다.
// 불필요한 어노테이션
const name: string = "철수"; // "철수"를 보면 string임을 알 수 있음
const count: number = 0; // 0을 보면 number임을 알 수 있음
const scores: number[] = [1, 2, 3]; // 배열 내용이 있으면 추론 가능
// 충분히 명확한 코드 (어노테이션 생략 권장)
const name = "철수";
const count = 0;
const scores = [1, 2, 3];
TypeScript는 코드가 놓인 위치의 문맥을 보고 타입을 추론하기도 합니다.
const names = ["철수", "영희", "민수"];
// forEach 콜백의 매개변수 타입이 자동으로 string으로 추론됨
names.forEach((name) => {
console.log(name.toUpperCase()); // 정상 — name이 string임을 알고 있음
});
name에 어노테이션을 붙이지 않았지만, names가 string[]이므로 콜백 매개변수도 string으로 추론됩니다.
반환 타입은 추론에 맡겨도 되지만, 명시하면 의도치 않은 반환을 방지합니다.
// 반환 타입 없음 — 내부 구현에 따라 추론됨
function fetchUser(id: number) {
if (id > 0) {
return { name: "철수" };
}
// 반환문 없음 → undefined 반환 가능
}
// 반환 타입 명시 — 모든 경로에서 올바른 값을 반환하도록 강제
function fetchUser(id: number): { name: string } {
if (id > 0) {
return { name: "철수" };
}
return { name: "미확인" }; // 빠뜨리면 오류 발생
}
어노테이션과 추론은 대립하는 개념이 아닙니다. TypeScript가 타입을 충분히 알 수 있는 곳에서는 추론에 맡기고, 외부 계약(함수 인터페이스, 공개 API)이나 타입이 명확하지 않은 곳에서는 어노테이션으로 의도를 명시하는 것이 균형 잡힌 접근입니다.