JavaScript는 동적으로 타입이 지정되어 있으며, 이는 진정으로 유용합니다 — 빠르게 반복하고, 자유롭게 프로토타입을 만들고, 빌드 단계 없이 배포할 수 있습니다. 하지만 그 유연성에는 비용이 따릅니다: 전체 오류 클래스가 런타임에서만 나타납니다. 함수가 문자열을 기대하는데 undefined를 받으면, 새벽 2시에 프로덕션에서 앱이 폭발합니다. TypeScript는 JavaScript 위에 정적 타입 시스템을 추가하여 컴파일 시간에, 편집기에서, 배포 전에 정확히 그런 오류를 잡아냅니다. 이 글은 그것이 실제로 무엇을 의미하는지 설명합니다 — 실제 코드, 솔직한 트레이드오프, 과장 없이.
TypeScript가 실제로 무엇인가
TypeScript는 JavaScript의 상위 집합입니다: 모든 유효한 .js 파일은 .ts로도 유효합니다. 파일 이름을 변경하고, 타입 어노테이션을 하나도 추가하지 않아도 컴파일됩니다. TypeScript가 추가하는 것 — 타입 어노테이션, 인터페이스, 제네릭, 열거형 — 은 컴파일 시간에 완전히 제거됩니다. 출력은 일반 JavaScript입니다. TypeScript는 실행되는 것을 변경하지 않습니다; 실행 전에 알 수 있는 것을 변경합니다.
컴파일러는 tsc이며, npm을 통해 설치됩니다. .ts 파일을 지정하면 런타임(Node.js, 브라우저, Deno)이 실행할 수 있는 .js 파일을 출력합니다. TypeScript 특정 구문은 절대 프로덕션에 도달하지 않습니다 — 타입은 개발자와 툴체인을 위해서만 존재합니다. GitHub의 TypeScript 저장소 자체가 대형 TypeScript 프로젝트로, 어떻게 확장되는지 감을 줍니다.
# Compile a single file
npx tsc src/index.ts
# Compile a whole project (uses tsconfig.json)
npx tsc
# Watch mode — recompile on every save
npx tsc --watch핵심 이점: 런타임 전에 오류 발견
다음은 TypeScript가 가장 눈에 띄게 해결하는 문제입니다. API 응답에서 필드가 문자열이거나 null일 수 있다고 가정해 보세요. 일반 JavaScript에서는 프로덕션까지 조용히 실패합니다:
// JavaScript — no error until this actually runs with a null value
function formatDisplayName(user) {
return user.displayName.toUpperCase(); // 💥 TypeError if displayName is null
}
// This looks fine in isolation. The bug only appears when a user
// has no display name set — which might be rare, but it will happen.
const name = formatDisplayName({ displayName: null });TypeScript는 코드가 실행되기 전에 이것을 잡아냅니다:
interface User {
id: number;
displayName: string | null;
email: string;
}
function formatDisplayName(user: User): string {
return user.displayName.toUpperCase();
// ❌ TypeScript error: Object is possibly 'null'.
// Property 'toUpperCase' does not exist on type 'null'.
}
// Fix: handle the null case explicitly
function formatDisplayName(user: User): string {
if (user.displayName === null) {
return user.email; // fall back to email
}
return user.displayName.toUpperCase(); // TypeScript now knows this is safe
}이것이 대부분의 개발자에게 아하 순간입니다. 오류 메시지는 정확히 무엇이 잘못되었고 어디에 있는지 알려줍니다 — 한 줄도 실행하기 전에, 편집기에서. strict 모드를 사용하면 TypeScript는 null과 undefined 문제를 잡는 데 특히 적극적입니다. strict 모드에서 기본적으로 켜지는 strictNullChecks 기능을 통해서입니다.
tsc의 오류는 코드에 타입 불일치가 있다는 것을 의미합니다 — TypeScript가 실행하지 않고도 실패할 것임을(또는 실패할 수 있음을) 증명할 수 있습니다. 타입 오류를 수정하면 해당 버그 클래스를 완전히 제거한 것입니다.TypeScript의 타입 시스템 한눈에 보기
모든 것에 어노테이션을 달 필요는 없습니다. TypeScript는 할당, 반환 값, 함수 호출에서 타입을 추론합니다 — 종종 그냥 일반 JavaScript를 작성하면 무료로 타입 검사를 받을 수 있습니다. 하지만 추론이 충분하지 않을 때 어노테이션 구문을 아는 것이 도움이 됩니다.
// Primitives
const productName: string = 'Wireless Keyboard';
const price: number = 79.99;
const inStock: boolean = true;
// TypeScript infers these without annotations — same effect
const productName = 'Wireless Keyboard'; // inferred: string
const price = 79.99; // inferred: number
// Arrays
const tags: string[] = ['electronics', 'peripherals'];
const ratings: number[] = [4.5, 4.8, 4.2];
// Objects with interfaces
interface Product {
id: number;
name: string;
price: number;
category: string;
inStock: boolean;
description: string | null; // union type — string or null
}
// Union types — a value that could be one of several types
type Status = 'active' | 'inactive' | 'pending'; // string literal union
type ID = string | number; // string or number
// Function with typed parameters and return type
function formatProductCard(product: Product): string {
const stockLabel = product.inStock ? 'In Stock' : 'Out of Stock';
const desc = product.description ?? 'No description available';
return `${product.name} — $${product.price.toFixed(2)} (${stockLabel})
${desc}`;
}
// Generic function — works with any type, preserves it
function firstOrDefault<T>(items: T[], fallback: T): T {
return items.length > 0 ? items[0] : fallback;
}
const first = firstOrDefault(['a', 'b', 'c'], 'z'); // inferred: string
const num = firstOrDefault([1, 2, 3], 0); // inferred: numberTypeScript 핸드북은 타입 시스템에 대해 깊이 다룹니다 — 정말 잘 쓰여졌으며 기본을 익힌 후 읽어볼 가치가 있습니다.
프로젝트에서 TypeScript 설정하기
프로젝트에 TypeScript를 추가하는 데는 약 5분이 걸립니다. 개발 의존성으로 컴파일러를 설치하고, 구성 파일을 생성하고, 적절한 속도로 .js 파일을 .ts로 이름을 바꾸기 시작합니다.
# Install TypeScript as a dev dependency
npm install -D typescript
# Generate tsconfig.json with sensible defaults
npx tsc --init생성된 tsconfig.json에는 대부분 주석 처리된 수십 가지 옵션이 있습니다. 알아야 할 주요 옵션들:
{
"compilerOptions": {
"target": "ES2020", // what JavaScript version to output
"module": "commonjs", // module system (commonjs for Node, ESNext for bundlers)
"outDir": "./dist", // where compiled .js files go
"rootDir": "./src", // where your .ts source files live
"strict": true, // enables all strict type checks — highly recommended
"esModuleInterop": true, // smoother interop with CommonJS modules
"skipLibCheck": true // skip type checks on .d.ts files in node_modules
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}새 프로젝트에서는 항상 strict: true를 활성화하세요. strictNullChecks, noImplicitAny, strictFunctionTypes를 포함한 여러 검사를 묶어 놓았습니다. 기존 코드베이스에서는 점진적으로 활성화해야 할 수도 있지만 — 새로운 것이라면 엄격하게 시작하세요.
TypeScript vs JavaScript — 실제 트레이드오프
TypeScript는 많은 진정한 이점을 가지고 있지만, 실제 비용도 있습니다. 솔직한 분석입니다:
- 실제로 작동하는 자동 완성. 편집기가 모든 객체의 형태를 알기 때문에, 추측 대신 올바른 속성 이름과 메서드 서명을 제안할 수 있습니다. 이것만으로도 매일 시간을 절약합니다.
- 프로덕션에 도달하기 전에 잡힌 버그. Null 역참조, 잘못된 인수 타입, 누락된 필수 속성 — TypeScript는 오전 3시 사고 채널이 아닌 작성 시간에 이것들을 표면화합니다.
- 더 안전한 리팩토링. 인터페이스의 필드 이름을 변경하면 TypeScript가 즉시 업데이트가 필요한 모든 호출 사이트를 표시합니다. 대형 JavaScript 코드베이스에서 그런 변경은 무섭습니다.
- 코드가 자체 문서화됩니다.
sendEmail(to: string, subject: string, body: string, options?: EmailOptions): Promise<SendResult>와 같은 함수 서명은 구현을 읽지 않아도 필요한 모든 것을 알려줍니다. - 팀에 더 유리합니다. 여러 개발자가 코드베이스를 공유할 때, 타입은 모듈 간의 계약을 형성합니다. 동료의 코드를 깨지 않고 자신의 코드를 리팩토링할 수 있습니다.
그리고 솔직한 비용:
- 빌드 단계가 추가됩니다. 소규모 스크립트나 빠른 프로토타입의 경우, 테스트하기 전에
tsc를 실행하는 것은 실제 마찰입니다. 바닐라 JavaScript는 즉시 실행됩니다. - 초기 설정에 시간이 걸립니다.
tsconfig.json구성, 서드파티 라이브러리의 타입 정의 추가(@types/express등), 편집기를 올바르게 구성하는 것은 몇 시간의 작업입니다. any가 모든 것을 약화시킵니다. TypeScript에는 탈출구가 있습니다 — 모든 것을any로 타입 지정할 수 있으며, 이는 해당 값에 대한 타입 검사를 비활성화합니다.any의 과도한 사용은 안전 없이 TypeScript의 마찰만 얻는다는 것을 의미합니다. 해결책이 아닌 목발입니다.- 서드파티 라이브러리 타입이 지연될 수 있습니다. 모든 npm 패키지가 TypeScript 정의를 함께 배포하지는 않습니다. 일부는 불완전하거나 최신이 아닐 수 있는 커뮤니티 유지
@types/*패키지에 의존합니다.
TypeScript가 진정으로 빛을 발하는 곳
TypeScript는 세 가지 상황에서 가장 큰 배당금을 줍니다: 대형 코드베이스, 공유 라이브러리, 그리고 시간이 지남에 따라 리팩토링될 모든 것. 타입 시스템은 항상 최신 상태인 살아있는 문서 역할을 합니다 — 주석이나 README 파일과 달리.
실제 예시: 앱이 사용자 데이터를 반환하는 REST API를 호출합니다. TypeScript 없이는 API가 예상한 것을 반환할 것을 신뢰합니다. TypeScript로는 응답을 모델링하고 일치하지 않는 경우 즉각적인 피드백을 받습니다:
interface ApiUser {
id: number;
username: string;
email: string;
avatarUrl: string | null;
createdAt: string; // ISO 8601 date string
role: 'admin' | 'editor' | 'viewer';
}
interface ApiResponse<T> {
data: T;
total: number;
page: number;
perPage: number;
}
async function fetchUsers(page: number): Promise<ApiResponse<ApiUser[]>> {
const response = await fetch(`/api/users?page=${page}`);
if (!response.ok) {
throw new Error(`Failed to fetch users: ${response.status}`);
}
return response.json() as Promise<ApiResponse<ApiUser[]>>;
}
// Now every consumer of this function knows exactly what they'll get
const result = await fetchUsers(1);
result.data.forEach(user => {
// TypeScript knows user.role is 'admin' | 'editor' | 'viewer'
// It will error if you try to access a property that doesn't exist
console.log(`${user.username} (${user.role})`);
});타입 검사를 일시적으로 선택 해제해야 할 때 any 대신 unknown을 사용하세요. 차이점: any는 모든 검사를 조용히 우회하는 반면, unknown은 값을 사용하기 전에 타입을 좁히도록 강제합니다 — 안전을 완전히 잃지 않으면서 탈출구를 얻습니다.
// ❌ any — TypeScript trusts you completely, no checks
function processData(data: any) {
data.nonExistentMethod(); // no error — TypeScript looks away
}
// ✅ unknown — you have to prove what it is before using it
function processData(data: unknown) {
if (typeof data === 'string') {
console.log(data.toUpperCase()); // safe — TypeScript knows it's a string here
} else if (Array.isArray(data)) {
console.log(data.length); // safe — TypeScript knows it's an array
}
}TypeScript 플레이그라운드는 이 패턴들을 실험하는 가장 빠른 방법입니다. 코드를 붙여넣고, 컴파일된 JavaScript 출력과 타입 오류를 실시간으로 확인할 수 있습니다 — 설치가 필요 없습니다.
마무리
TypeScript는 마법이 아니며, 모든 프로젝트에 적합한 것도 아닙니다. 하지만 성장하는, 한 명 이상이 유지 관리하는, 또는 리팩토링될 JavaScript 코드베이스에는 개발 경험을 진정으로 변화시킵니다. 타입 시스템은 배포 전에 전체 런타임 버그 카테고리를 잡고, 자동 완성을 실제로 유용하게 만들고, 리팩토링을 위험한 수동 프로세스에서 툴체인이 처리하는 것으로 바꿉니다.
공식 TypeScript 문서로 시작하세요 — 잘 구조화되어 있고 초보자 친화적입니다. TypeScript 플레이그라운드는 설정 없이 실험하기 좋습니다. MDN TypeScript 용어 항목은 두 번째 관점을 원한다면 견실한 단일 페이지 개요입니다. TypeScript에서 JSON 데이터를 다루고 있다면, 이 사이트의 JSON to TypeScript 도구가 JSON 페이로드에서 직접 TypeScript 인터페이스를 생성합니다 — 수동으로 타입을 작성하지 않고도 API 응답을 빠르게 모델링할 때 유용합니다.