TypeScript vs JavaScript — 2026년 어떤 것을 선택할까
타입 시스템, 생산성, 런타임, 에코시스템을 비교하고 프로젝트 규모별 추천.
한 줄 요약: 2026년 기준, 팀 규모 5명 이상이거나 코드베이스가 3개월 이상 유지될 프로젝트라면 TypeScript가 기본값이다. 단독 개발 소규모 스크립트나 빠른 프로토타이핑이라면 JavaScript에서 시작해도 늦지 않는다.
- 신규 프로젝트 스택을 결정해야 하는 팀 리드·아키텍트
- JavaScript로 시작했으나 TypeScript 전환을 고려 중인 개발자
- 주니어 개발자 온보딩에서 어떤 언어부터 시작할지 결정이 필요한 경우
- 레거시 JS 프로젝트의 TS 마이그레이션 타당성을 검토하는 경우
타입 시스템 — 런타임 전에 잡을 수 있는 버그의 범위
TypeScript의 정적 타입 시스템이 실제로 잡아내는 버그 유형을 구체적으로 파악해야 전환 가치를 판단할 수 있다. 단순히 "타입 오류를 잡는다"는 설명은 실무 판단에 부족하다.
TypeScript가 컴파일 타임에 잡는 버그:
- null/undefined 역참조 —
user.name에서user가 null일 수 있음 - 함수 인자 순서 바꿈 —
setRange(end, start)실수 - API 응답 구조 변경 후 누락된 수정 — 인터페이스가 전파됨
- 리팩토링 후 누락된 케이스 — 유니온 타입 exhaustive check
- 잘못된 프로퍼티 이름 접근 —
user.adress오타
JavaScript에서도 잡을 수 있는 것: ESLint + JSDoc 타입 주석 + // @ts-check를 조합하면 TypeScript 없이도 IDE 레벨 타입 힌트와 일부 정적 분석이 가능하다. 단, 복잡한 제네릭·조건부 타입·exhaustive check는 JSDoc으로 표현하기 어렵다.
TypeScript가 실제로 잡는 버그 예시// JavaScript에서 런타임 오류가 될 코드 function processUser(user) { return user.profile.avatar.url; // user.profile이 null이면 런타임 오류 } // TypeScript — 컴파일 타임에 오류 감지 interface User { profile: UserProfile | null; } interface UserProfile { avatar?: AvatarInfo; // 선택적 필드 } interface AvatarInfo { url: string; } function processUser(user: User): string | undefined { return user.profile?.avatar?.url; // 옵셔널 체이닝 강제 } // Union 타입 exhaustive check type Status = 'pending' | 'success' | 'error'; function handleStatus(status: Status) { switch (status) { case 'pending': return '처리 중'; case 'success': return '완료'; // 'error' 누락 시 TypeScript가 오류 발생 default: const _: never = status; // exhaustive guard } }
생산성 — 초기 비용 vs 장기 이득
TypeScript 도입의 초기 비용과 장기 이득을 정직하게 비교해야 한다. 타입 정의 작성, 빌드 설정, 서드파티 타입 패키지(@types/...) 관리가 추가된다. 반면 장기적으로는 리팩토링 안전성, IDE 자동완성, 코드 리뷰 시간이 줄어든다.
TypeScript가 생산성을 실제로 올리는 시나리오:
- 6개월 이상 된 코드베이스에서 낯선 함수를 수정할 때 — 타입이 문서 역할을 함
- 팀원이 교체되거나 온보딩할 때 — 코드 탐색 속도가 빠름
- API 스펙이 바뀔 때 — 영향 범위를 컴파일러가 추적
- 대규모 리팩토링 — 변경 후 남은 오류를 컴파일러가 나열
JavaScript가 더 빠른 시나리오:
- 1~2주 안에 폐기될 프로토타입
- 타입 정의가 없는 서드파티 라이브러리를 많이 쓰는 경우 (any 남발로 타입이 무의미해짐)
- 팀원이 TypeScript에 익숙하지 않아 타입 오류 해결에 더 많은 시간 소요
런타임과 에코시스템 — 2026년 현황
2026년 기준 에코시스템 현황에서 몇 가지 중요한 변화가 있다.
TypeScript 네이티브 지원 확산: Deno는 TypeScript를 트랜스파일 없이 직접 실행한다. Bun도 TypeScript를 빌드 없이 실행 지원. Node.js 22+는 --experimental-strip-types 플래그로 .ts 파일을 직접 실행 가능하다. 빌드 단계 없이 TypeScript를 사용하는 환경이 늘어났다.
타입 정의 커버리지: DefinitelyTyped에는 8,000개 이상의 @types 패키지가 있다. 주요 라이브러리 대부분이 자체 타입 정의를 번들링한다. 단, 오래된 라이브러리나 틈새 라이브러리는 여전히 타입 정의가 없거나 부정확할 수 있다.
번들러 지원: Vite, esbuild, Rollup, webpack 모두 TypeScript를 기본 지원한다. 별도 설정 비용이 3~4년 전보다 크게 줄었다.
Node.js에서 TypeScript 직접 실행 (Node 22+)# Node.js 22+ — 실험적 TypeScript 직접 실행 node --experimental-strip-types script.ts # tsconfig.json 없이도 동작 (기본 설정으로) # 타입 오류는 무시하고 실행 (타입 체크는 tsc --noEmit으로 별도 실행) # 권장 워크플로우 (타입 체크 + 실행 분리) npx tsc --noEmit # 타입 오류만 확인 node --experimental-strip-types src/index.ts # 실행 # Bun — 빌드 없이 TS 실행 bun run src/index.ts # Deno — 빌드 없이 TS 실행 deno run src/index.ts
학습 곡선 — 어디서 막히는가
TypeScript를 처음 배울 때 진짜로 어려운 부분은 기본 타입 지정이 아니다. 오히려 다음 개념에서 막히는 경우가 많다.
1. 제네릭: 함수나 클래스가 다양한 타입을 처리할 때 사용하는 타입 매개변수. 기존 JavaScript 개발자가 처음 접하는 추상화 레벨이라 직관적이지 않다.
2. 조건부 타입: T extends U ? X : Y 형태로 타입을 분기. 라이브러리 코드나 복잡한 유틸리티 타입을 이해할 때 필요하다.
3. 타입 가드: 유니온 타입에서 특정 타입을 좁히는(narrowing) 패턴. typeof, instanceof, 사용자 정의 타입 가드 함수.
4. Declaration merging / Module augmentation: 서드파티 라이브러리 타입을 확장할 때 필요. 프레임워크 플러그인 개발 시 자주 등장.
기본 타입 지정과 인터페이스 정의만으로 시작하면 학습 부담이 크지 않다. 제네릭은 직접 작성하기보다 라이브러리 타입을 읽는 것부터 익히는 것이 효율적이다.
프로젝트 규모별 추천 — 기준과 판단 트리
| 상황 | 추천 | 이유 |
|---|---|---|
| 1인 프로토타입 (2주 이내) | JavaScript | 타입 설정 비용 대비 이득이 없음 |
| 1인 장기 프로젝트 (3개월+) | TypeScript | 혼자여도 미래의 자신이 코드를 못 알아봄 |
| 팀 2~4인, 6개월+ | TypeScript | 리뷰·온보딩 비용 절감이 타입 정의 비용 초과 |
| 팀 5인 이상 | TypeScript 필수 | JS 코드베이스는 규모 커질수록 수정 위험 증가 |
| 공개 라이브러리/패키지 | TypeScript 필수 | 사용자 IDE 지원을 위해 타입 정의 필수 |
| Node.js 스크립트/자동화 | JavaScript or JSDoc | 빌드 없이 실행 가능한 단순함이 중요 |
| 주니어 온보딩용 프로젝트 | TypeScript (strict 아닌 기본) | 타입 오류가 학습 피드백 역할을 함 |
마이그레이션 전략: 기존 JS 프로젝트를 전환할 때는 tsconfig.json에서 allowJs: true + checkJs: true로 시작해 파일 단위로 .ts로 변환하는 점진적 전환이 현실적이다. 한 번에 전환하면 타입 오류가 수백 개 쏟아져 팀 사기가 꺾인다.
any를 무분별하게 쓰면 JS보다 나을 게 없다. tsconfig.json에서 "noImplicitAny": true와 "strict": true를 켜야 타입 시스템이 실제로 작동한다. 처음에 어렵더라도 strict 모드로 시작하는 것이 장기적으로 낫다.