🔥 첫 타입 주석: let, function, return

tsconfig.json 을 한 번 정해 두고 나서 처음으로 직접 적는 .ts 파일은 한 줄짜리 변수 선언인 경우가 많다. 나도 그랬다. bun init 으로 만든 디렉토리에서 hello.ts 를 열고 let name = "Mejoo" 한 줄을 적었더니 호버에 string 이라고 떴다. 그다음에 나는 let name: string = "Mejoo" 로 콜론 + 타입을 직접 적었다. 실행 결과는 같았다. 그래서 두 줄 사이에서 무엇이 달라졌는지를 한참 살폈다.
콜론은 TypeScript 의 타입 주석을 표시하는 단 하나의 기호다. 변수 선언 옆, 함수 매개변수 옆, 함수 반환 타입 옆 세 곳에서 같은 의미로 작동한다.
변수 옆에 콜론을 적는다
가장 단순한 형태는 let 다음에 변수 이름을 쓰고, 그 뒤에 콜론과 타입을 붙이는 단계다. string, number 처럼 TypeScript 가 미리 알고 있는 원시 타입 이름이면 충분하다.

// file: var-annotation.ts
export {};
let name: string = "Mejoo";
let count: number = 3;
console.log(name, count);
// 잘못된 대입 한 줄. 주석을 풀면 컴파일러가 멈춘다.
// let count2: number = "3";// file: var-annotation.ts
export {};
let name: string = "Mejoo";
let count: number = 3;
console.log(name, count);
// 잘못된 대입 한 줄. 주석을 풀면 컴파일러가 멈춘다.
// let count2: number = "3";bunx tsc --noEmit 으로 검사하면 두 줄 모두 통과한다. bunx tsx var-annotation.ts 로 실행하면 Mejoo 3 한 줄이 나온다. 마지막 주석 줄을 풀면 컴파일러는 Type 'string' is not assignable to type 'number'. 메시지를 띄우고 멈춘다.
콜론은 한 가지 약속을 적는다. 이 변수는 이 타입의 값만 받는다. 한 번 적어 두면 같은 변수를 다른 타입으로 다시 대입할 수 없다. 주석을 직접 적지 않은 변수도 TypeScript 가 초깃값을 보고 같은 타입으로 추론한다. 그래도 의도를 분명히 남기고 싶을 때, 또는 초깃값보다 더 좁은 타입을 강제하고 싶을 때는 콜론을 직접 적는다.

함수 시그니처 두 곳
함수 한 개에는 콜론이 두 종류로 들어간다. 매개변수 옆에 한 번씩, 그리고 매개변수 목록을 닫는 괄호 뒤에 한 번. 앞쪽은 매개변수가 받을 타입을 약속하고, 뒤쪽은 함수가 돌려줄 값의 타입을 약속한다.

// file: param-return.ts
export {};
function add(a: number, b: number): number {
return a + b;
}
console.log(add(2, 3));
// 잘못된 호출 한 줄. 주석을 풀면 호출 줄에서 컴파일러가 멈춘다.
// console.log(add("2", 3));// file: param-return.ts
export {};
function add(a: number, b: number): number {
return a + b;
}
console.log(add(2, 3));
// 잘못된 호출 한 줄. 주석을 풀면 호출 줄에서 컴파일러가 멈춘다.
// console.log(add("2", 3));bunx tsc --noEmit 은 그대로 통과하고, bunx tsx param-return.ts 는 5 한 줄을 출력한다. 마지막 주석을 풀면 컴파일러는 add 호출의 첫 인자에서 Argument of type 'string' is not assignable to parameter of type 'number'. 메시지를 띄운다. 호출 쪽이 검사 대상이 된다는 점이 중요하다. 함수 본문에서는 a 와 b 를 number 로 다룬다. 그래서 a + b 같은 number 연산을 바로 쓸 수 있다.
반환 타입 주석은 두 가지 일을 한다. 첫째, 호출하는 쪽이 받을 값의 타입을 미리 적어 둔다. 둘째, 함수 본문이 그 타입의 값을 반환하는지 컴파일러가 다시 검사한다. function add(...): number 라고 적어 두고 본문이 return "5" 라고 돌려주려고 하면, 컴파일러는 그 줄에서 멈춘다. 반환 타입을 직접 적어 두면 함수의 의도가 또렷하게 남는다.

같은 콜론, 다른 단계
let name: string 의 콜론과 (a: number, b: number): number 의 콜론들은 같은 기호 한 개다. 이건 이 타입이다 라는 한 약속을 세 단계에 같은 표기로 적는다.
- 변수 선언: 변수 이름과 타입 이름 사이에 콜론.
- 함수 매개변수: 매개변수 이름과 타입 이름 사이에 콜론.
- 함수 반환 타입: 매개변수 목록을 닫는 괄호와 반환 타입 이름 사이에 콜론.
세 가지 모두 결과 .js 에는 한 글자도 남지 않는다. 콜론 + 타입은 컴파일 시점에만 검사되는 표시이고, bunx tsx 로 실행할 때는 콜론과 타입을 모두 떼어 낸 자바스크립트 코드가 돌아간다. TypeScript 의 타입 주석은 항상 그렇게 컴파일러 쪽에서만 일을 한다.
세 단계가 같은 기호를 쓰기 때문에, 한 단계에서 익힌 질문을 나머지 두 단계에도 그대로 쓴다. 콜론을 본 줄에서는 왼쪽 이름이 어떤 타입을 갖는가 한 가지만 묻는다. let name: string 이면 name 이 string 이다. a: number 면 a 가 number 다. (): number 면 함수가 돌려주는 값이 number 다. 같은 질문에 같은 표기로 답한다.

다음 단계로 가기 전에
콜론 + 타입을 한 번 익숙하게 적기 시작하면, 다음으로 자연스럽게 떠오르는 질문이 있다. 언제 적고 언제 추론에 맡기는가. TypeScript 는 변수 선언 단계에서 초깃값을 보고 타입을 자동으로 추론한다. 그래서 모든 단계에 콜론을 적을 필요는 없다.
지금은 변수 선언과 함수 시그니처에 적은 콜론이 모두 같은 일을 한다는 점만 기억하면 충분하다.










