같은 코드, 다른 안전성
JavaScript와 TypeScript로 동일한 기능을 작성해보면 차이가 명확하게 드러납니다. 간단한 "사용자 목록에서 이메일 찾기" 함수를 비교해보겠습니다.
JavaScript 버전
function findEmail(users, name) { const user = users.find(u => u.name === name); return user.email;}const users = [ { name: "김민수", email: "[email protected]" }, { name: "이영희", email: "[email protected]" },];console.log(findEmail(users, "박철수")); // TypeError: Cannot read properties of undefined
"박철수"는 목록에 없으므로 find가 undefined를 반환합니다. undefined.email에 접근하는 순간 런타임 에러가 발생합니다. 이 에러는 코드를 실행해야만 알 수 있습니다.
TypeScript 버전
interface User { name: string; email: string;}function findEmail(users: User[], name: string): string | undefined { const user = users.find(u => u.name === name); return user?.email;}const users: User[] = [ { name: "김민수", email: "[email protected]" }, { name: "이영희", email: "[email protected]" },];console.log(findEmail(users, "박철수")); // undefined (에러 없음)
TypeScript 버전에서는 두 가지가 달라졌습니다.
users: User[]— 매개변수가User배열임을 명시했습니다user?.email—user가undefined일 수 있다는 것을 TypeScript가 알려줘서, 안전한 접근 연산자(?.)를 사용했습니다
TypeScript는 find의 반환값이 User | undefined라는 것을 알고 있습니다. 만약 user.email이라고 쓰면 에디터에서 바로 경고해줍니다.
VS Code 자동완성의 차이
TypeScript의 가장 체감되는 장점은 에디터의 자동완성입니다.
JavaScript에서의 자동완성
const user = { name: "김민수", age: 25, email: "[email protected]" };user. // ← 여기서 점을 찍으면?
JavaScript에서는 에디터가 user의 속성을 추측합니다. 때로는 정확하지만, 함수의 매개변수나 API 응답처럼 복잡한 경우에는 자동완성이 제대로 작동하지 않습니다.
TypeScript에서의 자동완성
interface User { name: string; age: number; email: string;}const user: User = { name: "김민수", age: 25, email: "[email protected]" };user. // ← 여기서 점을 찍으면 name, age, email이 정확하게 나옵니다
TypeScript에서는 User 인터페이스에 정의된 속성이 100% 정확하게 자동완성됩니다. 오타를 칠 일이 없어지고, 어떤 속성이 있는지 문서를 찾아볼 필요도 없습니다.
컴파일 시점 vs 런타임
JavaScript와 TypeScript의 에러 발견 시점을 비교하면 다음과 같습니다.
JavaScript의 에러 발견 과정
코드 작성 → 저장 → 실행 → 에러 발생! → 원인 추적 → 수정
↑
여기서야 알게 됩니다
TypeScript의 에러 발견 과정
코드 작성 → 에러 표시! → 즉시 수정 → 저장 → 실행 → 정상 동작
↑
여기서 바로 알려줍니다
TypeScript는 코드를 작성하는 순간 에러를 표시합니다. 저장하기도 전에, 실행하기도 전에, 문제가 있는 곳에 빨간 밑줄이 그어집니다.
TypeScript는 JavaScript의 확장입니다
한 가지 중요한 사실이 있습니다. 모든 JavaScript 코드는 유효한 TypeScript 코드입니다.
// 이 코드는 JavaScript이면서 동시에 TypeScript입니다const greeting = "안녕하세요";console.log(greeting.toUpperCase());
TypeScript는 JavaScript를 대체하는 것이 아니라, JavaScript 위에 타입 시스템을 얹은 것입니다. 기존 JavaScript 코드를 그대로 사용할 수 있고, 필요한 부분에만 타입을 추가할 수 있습니다.
TypeScript 코드는 최종적으로 JavaScript로 변환(컴파일)되어 실행됩니다.
TypeScript (.ts) → 컴파일(tsc) → JavaScript (.js) → 실행(Node.js/브라우저)
타입 정보는 컴파일 과정에서 모두 제거됩니다. 브라우저나 Node.js가 실행하는 것은 순수한 JavaScript입니다. 타입은 개발 시점의 안전장치이며, 실행 시점에는 성능에 아무런 영향을 주지 않습니다.
핵심 차이 정리
| 항목 | JavaScript | TypeScript |
|---|---|---|
| 타입 지정 | 없음 (동적 타입) | 있음 (정적 타입) |
| 에러 발견 시점 | 실행 시 (런타임) | 작성 시 (컴파일 타임) |
| 자동완성 | 제한적 | 정확함 |
| 학습 곡선 | 낮음 | 약간 높음 |
| 실행 환경 | 브라우저/Node.js 직접 실행 | 컴파일 후 JavaScript로 실행 |
| 파일 확장자 | .js |
.ts |
| 기존 JS 코드 호환 | — | 100% 호환 |
언제 TypeScript를 선택할까
TypeScript가 특히 빛을 발하는 상황이 있습니다.
- 팀 프로젝트: 다른 사람이 작성한 함수의 매개변수 타입을 바로 알 수 있습니다
- 대규모 프로젝트: 파일이 많아질수록 타입 시스템의 가치가 커집니다
- API 연동: 서버에서 받아오는 데이터의 구조를 타입으로 정의하면 실수가 줄어듭니다
- 리팩터링: 변수나 함수의 이름을 바꿀 때, 영향받는 모든 곳을 자동으로 찾아줍니다
반면 간단한 스크립트나 일회성 코드에서는 JavaScript만으로 충분할 수 있습니다. 중요한 것은 상황에 맞는 선택을 하는 것입니다.
다음 챕터에서는 TypeScript를 둘러싼 생태계를 한눈에 살펴보겠습니다.