본문으로 건너뛰기
Advertisement

1.1 TypeScript란?

JavaScript로 대규모 애플리케이션을 개발하다 보면 특정 종류의 버그가 반복적으로 등장한다. 함수에 숫자 대신 문자열을 전달하거나, 존재하지 않는 속성에 접근하거나, undefined를 숫자처럼 사용하는 실수들이다. 이런 버그는 코드를 실행해 보기 전까지 알 수 없고, 프로젝트 규모가 커질수록 찾아내기가 점점 더 어려워진다. TypeScript는 바로 이 문제를 해결하기 위해 만들어졌다.

TypeScript의 정의

TypeScript는 Microsoft가 2012년에 공개한 오픈 소스 프로그래밍 언어다. 핵심 설계자는 C#을 만든 Anders Hejlsberg다. TypeScript의 공식 정의는 "JavaScript의 슈퍼셋(Superset)"이다.

슈퍼셋이란 기존 언어의 모든 문법을 포함하면서 추가 기능을 얹은 언어를 말한다. 즉, 모든 유효한 JavaScript 코드는 그대로 유효한 TypeScript 코드다. 기존 .js 파일의 확장자를 .ts로 바꿔도 컴파일 오류 없이 동작한다. 이 특성 덕분에 기존 JavaScript 프로젝트에 TypeScript를 점진적으로 도입할 수 있다.

TypeScript가 JavaScript 위에 얹은 핵심 기능은 **정적 타입 시스템(Static Type System)**이다.

동적 타입 vs 정적 타입

JavaScript는 동적 타입 언어다. 변수의 타입이 코드를 실행하는 시점(런타임)에 결정된다.

// JavaScript — 타입이 런타임에 결정된다
let value = 42; // number
value = "hello"; // string으로 변경 가능 — 오류 없음
value = true; // boolean으로 변경 가능 — 오류 없음

TypeScript는 정적 타입 언어다. 변수의 타입이 코드를 작성하는 시점(컴파일타임)에 결정된다.

// TypeScript — 타입이 컴파일타임에 결정된다
let value: number = 42;
value = "hello"; // 오류: Type 'string' is not assignable to type 'number'
value = true; // 오류: Type 'boolean' is not assignable to type 'number'

코드를 실행하기 전, 에디터에서 코드를 작성하는 순간에 오류가 표시된다. 이것이 정적 타입 시스템의 본질이다.

TypeScript 탄생 배경

2012년 당시 Microsoft는 내부적으로 대규모 JavaScript 애플리케이션을 개발하고 있었다. 수십만 줄의 JavaScript를 수백 명의 개발자가 함께 작업하는 환경에서 심각한 문제들이 나타났다.

문제 1: 코드 탐색 불가 함수 하나를 수정할 때 그 함수가 프로젝트 전체에서 어떻게 사용되는지 파악하기 어려웠다. 매개변수 이름을 바꾸거나 타입을 변경하면 다른 파일에서 어떤 영향이 생기는지 알 수 없었다.

문제 2: 리팩토링 위험 객체의 속성명을 변경하면 그 객체를 참조하는 모든 코드를 수작업으로 찾아 고쳐야 했다. 하나라도 빠뜨리면 런타임에서야 오류가 발생했다.

문제 3: IDE 지원 부재 JavaScript는 타입 정보가 없어서 IDE가 "이 변수에 어떤 메서드가 있는지"를 알 수 없었다. 자동완성이 제대로 동작하지 않았다.

문제 4: 암묵적 타입 변환 5 + "3" 같은 연산이 오류 없이 "53"을 반환한다. 의도하지 않은 타입 변환이 조용히 일어나 버그를 만들어냈다.

Anders Hejlsberg 팀은 이 문제들을 해결하기 위해 JavaScript에 타입 정보를 추가하는 언어를 설계했고, 그것이 TypeScript다.

컴파일 과정과 타입 지우개

TypeScript 파일(.ts)은 브라우저나 Node.js에서 직접 실행할 수 없다. TypeScript 컴파일러(tsc)를 통해 JavaScript 파일(.js)로 변환해야 한다.

.ts 파일 작성


tsc (TypeScript Compiler)
├─ 타입 검사 (Type Checking)
│ └─ 오류 발견 시 에러 메시지 출력
└─ 코드 변환 (Transpilation)
└─ 타입 어노테이션 제거
└─ .js 파일 생성


.js 파일 (순수 JavaScript)


Node.js / 브라우저에서 실행

중요한 개념이 하나 있다. 바로 **타입 지우개(Type Erasure)**다.

TypeScript의 타입 정보는 컴파일 후 완전히 제거된다. 최종 .js 파일에는 타입 어노테이션이 하나도 남지 않는다.

// 작성한 TypeScript 코드 (hello.ts)
function greet(name: string): string {
return `Hello, ${name}!`;
}

const user: string = "Alice";
console.log(greet(user));
// 컴파일된 JavaScript 코드 (hello.js)
function greet(name) {
return `Hello, ${name}!`;
}

const user = "Alice";
console.log(greet(user));

: string, : number 같은 타입 어노테이션이 모두 사라졌다. 타입 정보는 오직 개발 과정에서만 존재하며, 런타임에는 아무런 흔적도 남지 않는다.

TypeScript가 잡아내는 실제 JavaScript 버그

JavaScript 개발자라면 한 번쯤 마주쳤을 오류들이 있다.

undefined is not a function

// JavaScript — 런타임에 오류 발생
const user = {
name: "Alice",
getAge: function() { return 30; }
};

user.getname(); // TypeError: user.getname is not a function
// 실행해 봐야 오류를 알 수 있다
// TypeScript — 작성 즉시 오류 감지
const user = {
name: "Alice",
getAge: function(): number { return 30; }
};

user.getname();
// 오류: Property 'getname' does not exist on type '...'
// 'getAge'를 사용하려고 했습니까?

Cannot read properties of undefined

// JavaScript — API 응답에 없는 속성에 접근
function displayUserCity(user) {
console.log(user.address.city); // user.address가 undefined면 런타임 오류
}

displayUserCity({ name: "Bob" }); // TypeError 발생
// TypeScript — 접근 전에 오류 감지
interface User {
name: string;
address?: {
city: string;
};
}

function displayUserCity(user: User): void {
console.log(user.address.city);
// 오류: Object is possibly 'undefined'
// 올바른 코드: console.log(user.address?.city);
}

의도치 않은 타입 변환

// JavaScript — 조용히 잘못된 결과를 반환
function calculateTotal(price, quantity) {
return price * quantity;
}

const total = calculateTotal("100", 3); // "100100100" 아님, 300 반환 (number * number)
// 하지만 price가 "100abc"라면? NaN 반환
// 폼 입력값을 그대로 넘기면 자주 발생하는 버그
// TypeScript — 잘못된 입력을 컴파일타임에 차단
function calculateTotal(price: number, quantity: number): number {
return price * quantity;
}

const total = calculateTotal("100", 3);
// 오류: Argument of type 'string' is not assignable to parameter of type 'number'

정적 타입 시스템의 실질적 이점

컴파일타임 에러 감지

런타임에서만 발견할 수 있었던 버그를 코드 작성 시점에 발견한다. 배포 후 사용자가 오류를 만나는 상황을 사전에 막는다.

IDE 지원 향상

타입 정보가 있으면 IDE가 코드를 완벽히 이해할 수 있다. 그 결과:

  • 자동완성(IntelliSense): 객체의 속성, 함수의 매개변수, 반환 타입을 자동으로 제안한다
  • 즉시 에러 표시: 코드를 저장하기 전에 빨간 밑줄로 오류를 표시한다
  • 리팩토링 지원: 변수명/함수명 변경 시 참조하는 모든 위치를 안전하게 일괄 변경한다
  • 정의로 이동(Go to Definition): Ctrl+클릭으로 함수나 타입이 정의된 위치로 바로 이동한다
  • 사용 위치 찾기(Find All References): 함수나 타입이 사용된 모든 위치를 찾는다

리팩토링 안전성

대규모 코드베이스에서 함수 시그니처를 변경할 때, TypeScript는 그 함수를 사용하는 모든 코드에서 호환성 오류를 즉시 표시한다. 리팩토링의 영향 범위를 정확히 파악하고 안전하게 변경할 수 있다.

코드가 곧 문서

타입 어노테이션은 그 자체로 문서 역할을 한다.

// 타입 없이 — 이 함수는 무엇을 받고 무엇을 반환하나?
function processOrder(order, options) { ... }

// 타입 있음 — 한눈에 파악 가능
function processOrder(order: Order, options: ProcessOptions): Promise<OrderResult> { ... }

JS vs TS 비교

항목JavaScriptTypeScript
타입 결정 시점런타임컴파일타임
에러 발견 시점실행 후작성/컴파일 시
IDE 자동완성제한적완전 지원
리팩토링 안전성낮음높음
유지보수성대규모에서 어려움대규모에 적합
학습 곡선낮음중간
실행 환경직접 실행tsc 컴파일 후 실행
파일 확장자.js.ts, .tsx
기존 JS 코드그대로 사용 가능

실전 예제: JS 버그를 TS가 잡아내는 비교

실제 코드베이스에서 흔히 발생하는 시나리오를 비교해 본다.

시나리오: 쇼핑몰 할인 계산기

// JavaScript 버전 — 버그가 숨어있다
function applyDiscount(price, discountPercent) {
return price - (price * discountPercent / 100);
}

// 개발자가 실수로 문자열을 넘겼다
const itemPrice = "50000"; // 폼 입력에서 가져온 값
const discount = 10;

const finalPrice = applyDiscount(itemPrice, discount);
console.log(finalPrice); // 45000 — 우연히 맞는 결과 (string * number = number)

// 하지만 이런 경우엔?
const finalPrice2 = applyDiscount("5만원", 10);
console.log(finalPrice2); // NaN — 런타임에서야 발견
// TypeScript 버전 — 컴파일타임에 문제를 잡는다
function applyDiscount(price: number, discountPercent: number): number {
return price - (price * discountPercent / 100);
}

const itemPrice = "50000"; // 폼에서 가져온 문자열
const discount = 10;

const finalPrice = applyDiscount(itemPrice, discount);
// 오류: Argument of type 'string' is not assignable to parameter of type 'number'
// 컴파일 자체가 실패 — 배포 전에 반드시 수정해야 한다

// 올바른 사용
const finalPrice2 = applyDiscount(Number(itemPrice), discount);
console.log(finalPrice2); // 45000

시나리오: API 응답 처리

// JavaScript 버전
async function fetchUserProfile(userId) {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();

// API 응답 구조를 몰라도 바로 접근 가능하지만...
return `${data.firstName} ${data.lastName}`;
// 실제 API가 firstName/lastName 대신 first_name/last_name을 반환하면?
// "undefined undefined" — 런타임에서야 발견
}
// TypeScript 버전
interface UserProfile {
id: number;
firstName: string;
lastName: string;
email: string;
}

async function fetchUserProfile(userId: number): Promise<string> {
const response = await fetch(`/api/users/${userId}`);
const data: UserProfile = await response.json();

// IDE가 data의 속성을 정확히 알고 자동완성해 준다
return `${data.firstName} ${data.lastName}`;
// 만약 first_name으로 접근하려 하면?
// 오류: Property 'first_name' does not exist on type 'UserProfile'
}

TypeScript 생태계와 채택 현황

TypeScript는 현재 JavaScript 생태계에서 사실상 표준 언어로 자리 잡았다.

주요 프레임워크와 라이브러리

거의 모든 주요 JavaScript 프레임워크가 TypeScript로 작성되었거나 TypeScript를 공식 지원한다.

도구/프레임워크TypeScript 지원 수준
AngularTypeScript 전용 (처음부터 TS로 설계)
React.tsx 파일, 공식 타입 정의 제공
Vue 3TypeScript로 재작성, Composition API에 최적화
Next.jsTypeScript 기본 설정 제공
NestJSTypeScript 전용 Node.js 백엔드 프레임워크
DenoTypeScript를 기본 언어로 지원
BunTypeScript 직접 실행 지원

DefinitelyTyped — JavaScript 라이브러리에 타입 추가

TypeScript로 작성되지 않은 JavaScript 라이브러리도 TypeScript에서 타입 안전하게 사용할 수 있다. DefinitelyTyped 프로젝트가 수천 개의 라이브러리에 대한 타입 정의(.d.ts 파일)를 @types/xxx 패키지로 제공한다.

npm install --save-dev @types/node      # Node.js 내장 모듈 타입
npm install --save-dev @types/express # Express.js 타입
npm install --save-dev @types/lodash # lodash 타입
npm install --save-dev @types/jest # Jest 테스트 프레임워크 타입

최신 라이브러리들은 자체적으로 타입 정의를 포함한다. axios, zod, prisma 같은 라이브러리는 별도 @types 패키지 없이 바로 TypeScript를 지원한다.

타입 선언 파일 (.d.ts)

.d.ts 파일은 타입 정의만 담고 구현 코드는 없는 파일이다. TypeScript가 JavaScript 라이브러리의 API를 이해하기 위해 사용한다.

// node_modules/@types/node/index.d.ts 내용 일부 (예시)
declare module 'path' {
function join(...paths: string[]): string;
function resolve(...paths: string[]): string;
const sep: string;
}

이 파일 덕분에 import path from 'path'path.join을 호출하면 자동완성과 타입 검사가 동작한다.

TypeScript 버전 역사

TypeScript는 2012년 첫 공개 이후 꾸준히 발전해 왔다. 주요 이정표:

버전연도주요 기능
1.02014공식 GA 릴리즈
2.02016strictNullChecks, Tagged Union
3.02018프로젝트 레퍼런스, Tuples
4.02020Variadic Tuple, unknown in catch
4.52021Awaited<T>, 임포트 타입 수정자
5.02023데코레이터 표준화, const 타입 매개변수
5.42024NoInfer<T>, 클로저 타입 보강
5.52024추론된 타입 술어, 정규식 타입 검사

TypeScript 5.x 시리즈는 대규모 기능 추가보다 성능 향상과 정밀한 타입 추론 개선에 집중하고 있다.

고수 팁

TypeScript는 런타임에 영향을 주지 않는다

TypeScript의 타입 시스템은 순수하게 개발 도구다. 컴파일 후 최종 JavaScript에는 타입 정보가 전혀 남지 않으므로 런타임 성능에 영향을 주지 않는다. 번들 크기도 커지지 않는다.

타입 어노테이션은 "실행되는 코드"가 아니라 "개발자와 컴파일러 사이의 계약서"다.

타입은 개발 도구일 뿐

TypeScript가 타입 오류를 발견해도 대부분의 경우 컴파일은 계속 진행되어 JavaScript 파일을 만들어낸다. (noEmitOnError: true 옵션을 설정하면 오류 시 파일을 생성하지 않는다.) 타입 오류가 있는 코드도 일단 실행할 수 있다는 뜻이다.

이는 JavaScript 프로젝트를 TypeScript로 점진적으로 마이그레이션할 때 매우 유용하다.

모든 JS 라이브러리에 타입을 붙일 수 있다

JavaScript로 만들어진 기존 라이브러리도 TypeScript에서 사용할 수 있다. DefinitelyTyped 프로젝트(@types/xxx 패키지)가 수천 개의 JavaScript 라이브러리에 대한 타입 정의를 제공한다.

npm install --save-dev @types/node    # Node.js 타입 정의
npm install --save-dev @types/lodash # lodash 타입 정의

TypeScript는 '타입 지우개' 언어다

TypeScript = JavaScript + 타입 어노테이션 → 컴파일 → JavaScript (타입 제거)

이 사실을 이해하면 TypeScript를 두려워할 이유가 없다. TypeScript를 배운다는 것은 기존 JavaScript 위에 타입을 명시하는 방법을 배우는 것이다. JavaScript를 이미 안다면 TypeScript 문법의 80%는 이미 아는 셈이다.

정리

개념설명
TypeScriptJavaScript의 슈퍼셋, 정적 타입 시스템 추가
슈퍼셋모든 JS 코드가 유효한 TS 코드
정적 타입컴파일타임에 타입 결정 및 검사
tscTypeScript 컴파일러, .ts → .js 변환
타입 지우개컴파일 후 타입 정보는 제거됨
런타임 영향없음 — 타입은 개발 도구일 뿐
탄생Microsoft, 2012, Anders Hejlsberg
목적대규모 JS 프로젝트의 안정성·유지보수성 향상

다음 장에서는 TypeScript를 실제로 사용하기 위한 설치와 환경 설정 방법을 알아본다.

Advertisement