TypeScript는 JavaScript 위에 정적 타입 시스템을 더한 언어입니다. 모든 JavaScript 프로그램은 올바른 TypeScript 문법이고, TypeScript는 타입 표기와, 코드가 실행되기 전에 그것을 검사하는 컴파일러, 그리고 표기를 다시 지우는 빌드 단계를 더합니다. 런타임에는 JavaScript만 있으므로, 타입스크립트와 자바스크립트의 차이는 전적으로 배포하기 전에 무엇을 알아낼 수 있느냐에 있습니다.
JavaScript로 쓴 같은 함수는 이 코드에서 type Product = ..., : Product[], : number를 뺀 것입니다. 타입은 컴파일러와 에디터를 위한 정보를 더할 뿐, 프로그램이 하는 일을 바꾸지 않습니다.
TypeScript와 JavaScript 한눈에 비교
| JavaScript | TypeScript | |
|---|---|---|
| 타입 시스템 | 동적: 타입은 값에 속하며 런타임에만 알 수 있음 | 정적: 타입을 선언하거나 추론하고 컴파일 시점에 검사 |
| 타입 오류가 드러나는 때 | 그 줄이 실행될 때(undefined, NaN, TypeError) | 입력하는 동안 에디터에서, 그리고 컴파일할 때 |
| 실행 환경 | 브라우저, Node.js, Deno, Bun에서 바로 | 같은 곳에서, 타입을 제거한 뒤 |
| 빌드 단계 | 필요 없음 | tsc나 번들러, 또는 타입을 직접 제거하는 런타임 |
| 파일 | .js, .mjs, .cjs | .ts, .mts, .cts, .tsx, 그리고 .d.ts 타입 선언 |
| 실행 속도 | 기준 | 같음: 출력이 JavaScript |
| 에디터 지원 | 추론된 타입과 라이브러리 타입 정의에 기반한 자동 완성(불완전할 수 있음) | 선언된 타입에 기반한 자동 완성, 이름 바꾸기, "모든 참조 찾기" |
| 학습 난이도 | 낮음 | JavaScript에 타입 시스템을 더한 만큼 |
| 표준 | TC39의 ECMAScript | ECMAScript를 따르는 Microsoft의 오픈 소스 프로젝트 |
같은 코드를 두 언어로
JavaScript로 쓴 함수입니다. user가 어떤 모양이어야 하는지 아무 데도 적혀 있지 않습니다.
function greeting(user) {
return `Hello, ${user.firstName} ${user.lastName}`;
}
greeting({ firstname: "Ada", lastName: "Lovelace" });
// "Hello, undefined Lovelace", no error anywhere
TypeScript 버전은 모양을 한 번 밝혀 두고, 오타는 코드가 실행되기 전에 보고됩니다.
interface User {
firstName: string;
lastName: string;
}
function greeting(user: User): string {
return `Hello, ${user.firstName} ${user.lastName}`;
}
greeting({ firstname: "Ada", lastName: "Lovelace" });
// error TS2561: Object literal may only specify known properties,
// but 'firstname' does not exist in type 'User'. Did you mean to write 'firstName'?
문법상의 차이는 타입 표기가 전부입니다. TypeScript는 자체 선언 몇 가지(interface, type, enum, Array<string> 같은 제네릭, private 같은 접근 제한자)도 더하지만, 문장, 연산자, 내장 객체는 JavaScript의 것입니다.
JavaScript는 못 잡고 TypeScript는 잡는 것
JavaScript는 타입을 조용히 변환합니다. 이 버그는 항상 문자열인 폼 필드 값에서 흔히 생깁니다. 실행해서 컴파일러가 뭐라고 하는지 보세요.
index.ts(7,17): error TS2345: Argument of type 'string[]' is not assignable to parameter of type 'number[]'.
Type 'string' is not assignable to type 'number'.
JavaScript에서는 이 코드가 실행되어 010205를 출력합니다. 0 + "10"은 문자열 연결이기 때문입니다. TypeScript는 문자열을 변환할 때까지, 예를 들어 fromForm.map(Number)를 쓸 때까지 컴파일을 거부합니다.
또 하나의 큰 범주는 없을 수도 있는 값입니다. Array.prototype.find는 일치하는 것이 없으면 undefined를 반환하고, TypeScript는 이 경우를 처리하게 만듭니다.
index.ts(8,13): error TS18048: 'user' is possibly 'undefined'.
평범한 JavaScript 버전은 런타임에 TypeError: Cannot read properties of undefined (reading 'name')로 멈춥니다. TypeScript에서는 컴파일러가 가리킨 경우를 처리하면 됩니다.
출력:
GRACE
no user with id 3
TypeScript가 잡지 못하는 것: 논리 오류(잘못된 공식도 타입은 맞습니다), 그리고 런타임에 프로그램으로 들어오는 데이터에 관한 모든 것입니다. User로 타입이 지정된 API 응답은 그것을 보낸 서버만큼만 정확합니다. 코드가 실행될 때 타입은 이미 사라졌기 때문입니다. 그런 데이터는 런타임 코드로 검사하세요.
TypeScript에서 JavaScript 라이브러리 쓰기
출력이 어차피 JavaScript이므로 모든 npm 패키지는 TypeScript에서 동작합니다. 패키지의 타입은 세 곳 중 하나에서 옵니다.
- 패키지가 자체
.d.ts파일을 포함합니다. 활발하게 관리되는 대부분의 패키지가 그렇고, 따로 설치할 것이 없습니다. - 별도의
@types패키지 가 커뮤니티 프로젝트 DefinitelyTyped에서 제공됩니다.npm install --save-dev @types/lodash는lodash의 타입을 추가합니다. - 어디에도 없습니다. 그러면
strict가 켜져 있을 때 import 자체가 오류입니다.
error TS7016: Could not find a declaration file for module 'lodash'. '/project/node_modules/lodash/lodash.js' implicitly has an 'any' type.
Try `npm i --save-dev @types/lodash` if it exists or add a new declaration (.d.ts) file containing `declare module 'lodash';`
해결책은 @types 패키지가 있으면 설치하는 것이고, 없으면 .d.ts 파일에 모듈을 직접 기술하는 것입니다. 방법은 선언 파일 페이지에서 보여 줍니다.
빌드 단계
브라우저와 Node.js는 타입을 검사하지 않으므로, TypeScript에는 소스와 실행되는 코드 사이에 단계가 하나 필요합니다. 흔한 구성은 세 가지입니다.
tsc가 모두 컴파일합니다. 타입을 검사하고 보통dist폴더에.js파일을 씁니다. 단순하며 라이브러리의 표준입니다.- 번들러나 개발 서버가 타입을 제거하고,
tsc --noEmit이 검사합니다. Vite와 esbuild는 검사 없이 타입만 제거해서 다시 불러오기가 빠릅니다. 타입 검사는 에디터와 CI 단계가 맡습니다. - 런타임이 타입을 제거합니다. 최신 Node.js, Deno, Bun은
.ts파일을 바로 실행합니다. 셋 다 실행 중에 타입을 검사하지 않으므로, 타입 오류를 찾으려면 여전히tsc --noEmit(또는deno check)을 씁니다.
JavaScript에는 이 중 아무것도 필요 없고, 작은 스크립트에서는 이것이 가장 큰 실용적 장점입니다. TypeScript 단계의 비용은 대부분 설정(tsconfig.json과 typescript 개발 의존성)과 컴파일 시간입니다. TypeScript 7의 네이티브 컴파일러는 대규모 프로젝트에서 이 시간을 약 10분의 1로 줄였습니다.
학습 난이도
TypeScript의 런타임은 JavaScript이므로 JavaScript에 대해 아는 것은 전부 그대로 쓸 수 있습니다. 새로 배울 것은 타입 시스템이고, 층층이 쌓여 있습니다.
- 변수, 매개변수, 반환값의 타입 표기(
: string,: number[]). interface와type으로 만드는 객체 타입, 선택적 속성,string | number같은 유니언.- 좁히기:
typeof,in,===로 값을 확인해서 지금 어떤 경우인지 컴파일러가 알게 하기. - 제네릭,
Partial<T>와Pick<T, K>같은 유틸리티 타입, 라이브러리 작성자를 위한 고급 타입.
처음 두 층이면 대부분의 애플리케이션 코드를 다룹니다. 타입의 상당 부분은 추론되므로, 많은 TypeScript 코드는 함수 시그니처에만 표기가 있는 JavaScript처럼 보입니다.
TypeScript와 JavaScript 중 무엇을 고를까
TypeScript가 JavaScript보다 나을까요? 여러 사람이 유지보수하거나 몇 년씩 살아남는 코드라면 대개 그렇고, 업계도 그쪽으로 움직였습니다. GitHub의 월간 기여자 수 기준으로 TypeScript는 2025년 8월 JavaScript와 Python을 모두 제치고 GitHub에서 가장 많이 쓰이는 언어가 되었습니다. 작은 스크립트라면 평범한 JavaScript가 더 나은 도구인 경우가 많습니다.
TypeScript 를 고를 때:
- 여러 사람이 코드를 다루거나, 몇 달 또는 몇 년 동안 유지보수할 때.
- 모든 함수 시그니처를 머릿속에 담아 둘 수 없을 만큼 코드베이스가 클 때.
- 리팩터링을 자주 할 때: 속성 이름을 바꾸면 모든 사용처가 바뀌고, 남은 곳은 컴파일러가 알려 줍니다.
- 라이브러리를 배포할 때:
.d.ts파일이 사용자에게 자동 완성과 검사를 제공합니다. - 프레임워크가 기대할 때. Angular 앱은 TypeScript로 작성하고, Next.js와 Astro는 새 프로젝트를 기본으로 TypeScript로 만들며, Vite의 React, Vue, Svelte 템플릿에는 각각 TypeScript 버전이 있습니다.
JavaScript 를 고를 때:
- 프로그램이 짧은 스크립트, 일회성 실험, 브라우저 콘솔의 코드 조각일 때.
- 프로그래밍을 처음 배우고 있어서 한 번에 다룰 개념을 줄이고 싶을 때.
- 빌드 단계가 없고 만들고 싶지도 않을 때. 이 경우에도 JSDoc과
// @ts-check를 쓰면 평범한.js파일에서 어느 정도 검사를 받을 수 있습니다.
JavaScript 프로젝트를 TypeScript로 옮기기
마이그레이션을 한 번에 할 필요는 없습니다. 컴파일러는 TypeScript 옆의 JavaScript를 받아들입니다.
{
"compilerOptions": {
"allowJs": true,
"checkJs": false,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}
allowJs를 켜면 .js 파일이 컴파일되고, .ts 파일에서 import할 수 있으며 그 반대도 됩니다. 그다음 점진적으로 바꿉니다.
- 파일 하나의 이름을
.js에서.ts로 바꾸고, 컴파일러가 그 파일에서 보고하는 오류를 고칩니다. - 말단(import가 적은 유틸리티 모듈)부터 시작해서 안쪽으로 진행합니다.
checkJs를 켜거나 개별.js파일 맨 위에// @ts-check를 더해서, 아직 이름을 바꾸지 않은 파일도 타입 검사합니다.
검사되는 JavaScript 파일에서는 JSDoc 주석이 타입을 제공합니다.
// @ts-check
/**
* @param {number} price
* @param {number} qty
* @returns {number}
*/
function lineTotal(price, qty) {
return price * qty;
}
lineTotal("3", 2);
// error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
어떤 팀은 여기서 멈춥니다. JSDoc 타입이 붙은 JavaScript 파일을 tsc로 검사하고, 코드 자체에는 빌드 단계를 두지 않는 것입니다. 끝까지 .ts로 가는 팀도 있습니다. 기존 프로젝트에서 strict를 켜면 처음에는 오류가 많이 나올 것입니다. strict 모드 페이지에서 각 플래그가 무엇을 검사하는지 보고 하나씩 켜세요.
자주 묻는 질문
TypeScript와 JavaScript의 가장 큰 차이는 무엇인가요?
TypeScript는 JavaScript에 정적 타입을 더합니다. 각 값이 무엇인지(name: string, items: Item[]) 기술하면 TypeScript 컴파일러가 코드가 실행되기 전에 실수를 알려 줍니다. JavaScript는 미리 아무것도 검사하지 않아서, 잘못된 타입은 그 줄이 실행될 때에야 대개 undefined나 TypeError로 드러납니다.
TypeScript가 JavaScript보다 더 좋은가요?
여러 사람이 유지보수하거나 몇 주 이상 살아남는 대부분의 프로젝트에서는 그렇습니다. 타입은 여러 종류의 버그를 통째로 잡아내고, 리팩터링을 안전하게 만들고, 에디터 자동 완성을 가능하게 합니다. 짧은 스크립트, 빠른 프로토타입, 학습용 연습이라면 평범한 JavaScript가 시작이 빠르고 빌드 설정도 필요 없습니다.
TypeScript가 JavaScript보다 빠른가요?
아니요, 그렇다고 더 느리지도 않습니다. TypeScript는 JavaScript로 컴파일되고 타입은 지워지므로, 실행되는 코드는 손으로 작성한 JavaScript와 같습니다. 추가 비용은 개발 중의 컴파일 시간뿐입니다.
JavaScript와 TypeScript 중 무엇을 먼저 배워야 하나요?
JavaScript 기초를 먼저 배우거나 TypeScript와 함께 배우세요. 런타임 동작(변수, 함수, 객체, 배열, 프로미스)은 모두 JavaScript이고, TypeScript는 그것을 설명할 뿐입니다. 작은 JavaScript 프로그램을 쓸 수 있게 되면 타입을 더하는 것은 금방입니다.
한 프로젝트에서 TypeScript와 JavaScript를 함께 쓸 수 있나요?
네. tsconfig.json에 "allowJs": true를 설정하면 컴파일러가 .ts 파일 옆의 .js 파일도 받아들입니다. "checkJs": true(또는 파일마다 // @ts-check 주석)를 더하면 JSDoc 주석의 타입을 이용해 JavaScript 파일도 타입 검사합니다. 프로젝트를 파일 단위로 옮길 때 흔히 쓰는 방법입니다.