TypeScript를 사용하는 이유
JS에서 TS로의 이동
JavaScript는 1995년, Brendan Eich가 단 10일 만에 만든 언어입니다. 당시 목적은 단순했습니다. 버튼 클릭 시 팝업을 띄우는 수준의 인터랙션을 웹페이지에 추가하는 것이었습니다.
문제는 웹이 예상보다 훨씬 빠르게, 훨씬 크게 성장했다는 점입니다. 2009년 Node.js가 등장하면서 JS는 서버까지 진출했고, Angular·React·Vue 같은 SPA 프레임워크가 폭발적으로 확산되면서 프론트엔드 코드 규모는 수십만 줄에 달하게 되었습니다. 그런데 JS는 여전히 "10일 스크립트 언어"의 설계를 그대로 유지하고 있었습니다.
function getUser(id) {
// id가 숫자인지, 문자열인지, undefined인지 아무도 모릅니다
}
getUser() // 그냥 실행됩니다
getUser("abc") // 그냥 실행됩니다
getUser({}) // 그냥 실행됩니다 — 런타임에 터집니다 💀
결국 Microsoft가 2012년 해결책을 내놓았습니다. C#을 설계한 Anders Hejlsberg가 만든 TypeScript입니다. 핵심 철학은 단 하나였습니다.
"JS를 대체하지 않는다. JS에 타입만 얹는다."
2016년 Angular 2가 TypeScript를 공식 언어로 채택하면서 기업 프로젝트로 급속히 확산되었고, VS Code(그 자체가 TypeScript로 만들어진 에디터)가 업계 표준이 되면서 개발 경험의 차이가 눈에 띄게 벌어졌습니다. 오늘날 TypeScript는 Stack Overflow 개발자 설문에서 수년째 "가장 사랑받는 언어" 상위권을 차지하고 있습니다.
이 글에서는 JS를 이미 쓰고 있는 개발자를 위해, TypeScript의 핵심 문법을 실용적인 관점에서 소개해 보려고 합니다.
1. 타입 선언 기초: 변수와 함수에 타입 붙이기
TypeScript의 시작은 간단합니다. 변수와 함수에 타입을 명시하는 것입니다.
TypeScript 공식 문서에 따르면, TypeScript는 JavaScript의 모든 기능을 포함하며 그 위에 타입 시스템이라는 레이어를 추가합니다. 즉, 기존 JS 코드는 그대로 TS에서도 동작합니다.
// 변수 타입 선언
let username: string = "Alice";
let age: number = 30;
let isAdmin: boolean = false;
// 함수 파라미터와 반환 타입 선언
function greet(name: string): string {
return `안녕하세요, ${name}님!`;
}
greet(123); // 컴파일 에러 — number는 string에 할당 불가
greet("Bob"); // 가능
JS였다면 greet(123)은 그냥 실행되어 "안녕하세요, 123님!"을 반환했을 것입니다. TS는 이 실수를 코드를 작성하는 순간 잡아냅니다.
타입 추론(Type Inference) 덕분에 항상 타입을 직접 적을 필요는 없습니다. 값을 바로 할당하면 TypeScript가 자동으로 타입을 파악합니다.
let count = 10; // TypeScript가 number로 추론합니다
count = "열"; // 컴파일 에러 — 이미 number로 확정됨
2. Interface와 Type: 객체의 모양 정의하기
실무에서는 단순한 원시 타입보다 객체를 다루는 경우가 훨씬 많습니다. TypeScript는 interface와 type 두 가지 방식으로 객체의 구조를 정의할 수 있습니다.
TypeScript 공식 문서는 복잡한 타입을 만드는 두 가지 대표적인 방법으로 인터페이스(Interfaces)와 타입(Types)을 소개하고 있습니다. 일반적으로 객체 구조에는 interface를 권장하며, 특수한 기능이 필요할 때만 type을 사용하도록 안내하고 있습니다.
// Interface로 객체 구조 정의
interface User {
id: number;
name: string;
email: string;
role?: string; // ? 는 선택적 속성 (없어도 됩니다)
}
function displayUser(user: User): void {
console.log(`${user.name} (${user.email})`);
}
const alice: User = { id: 1, name: "Alice", email: "alice@example.com" };
displayUser(alice); // 가능
const broken = { id: 2, name: "Bob" }; // email 누락
displayUser(broken); // 컴파일 에러 — email 속성이 없습니다
type은 객체 외에도 유니온(Union) 타입 같은 복잡한 조합을 표현할 때 주로 사용합니다.
// Union 타입 — 여러 타입 중 하나를 허용합니다
type ID = number | string;
let userId: ID = 101;
userId = "user-abc"; // 둘 다 허용
// Literal 타입 — 특정 값만 허용합니다
type Status = "pending" | "approved" | "rejected";
function updateStatus(status: Status): void {
console.log(`상태 변경: ${status}`);
}
updateStatus("approved"); //
updateStatus("cancelled"); // 컴파일 에러 — 허용되지 않는 값
3. 제네릭(Generic): 타입을 파라미터처럼 다루기
제네릭은 처음에는 낯설게 느껴지지만, 한번 이해하면 TypeScript의 진가를 체감할 수 있는 기능입니다. 재사용 가능한 코드를 작성하면서도 타입 안정성을 유지하고 싶을 때 사용합니다.
예를 들어, API 응답을 처리하는 함수를 만든다고 가정해 보겠습니다.
// any를 쓰면 타입 안정성이 사라집니다
function fetchData(url: string): any {
// ...
}
// Generic을 쓰면 반환 타입을 호출할 때 지정할 수 있습니다
async function fetchData<T>(url: string): Promise<T> {
const response = await fetch(url);
return response.json();
}
interface Post {
id: number;
title: string;
}
// 호출 시 타입을 지정합니다
const post = await fetchData<Post>("/api/post/1");
console.log(post.title); // 자동완성 + 타입 체크가 됩니다
console.log(post.author); // 컴파일 에러: Post에 author가 없습니다
<T>는 타입 파라미터입니다. 함수를 호출할 때 T 자리에 원하는 타입을 넣으면, TypeScript가 그 타입으로 함수 전체를 검사합니다. any를 쓰면 이 검사가 모두 무력화된다는 점에서, 제네릭은 any의 올바른 대안이라고 할 수 있습니다.
4. strictNullChecks: null과 undefined를 다루는 법
JavaScript에서 가장 흔한 에러 중 하나는 Cannot read properties of undefined입니다. TypeScript의 strictNullChecks 옵션은 이 문제를 컴파일 단계에서 차단합니다.
TypeScript 공식 문서에 따르면, strictNullChecks를 true로 설정하면 null과 undefined가 독립적인 타입으로 취급되어, 이를 일반 값처럼 사용할 경우 타입 에러가 발생합니다.
// strictNullChecks: true 설정 시
const users = ["Alice", "Bob", "Charlie"];
const found = users.find(u => u === "Dave"); // string | undefined
console.log(found.toUpperCase()); // found가 undefined일 수 있어 에러 발생
// 해결 방법 1: 옵셔널 체이닝
console.log(found?.toUpperCase()); // undefined면 그냥 undefined 반환
// 해결 방법 2: 타입 가드
if (found) {
console.log(found.toUpperCase()); // 이 블록 안에서 found는 string
}
이처럼 TypeScript는 "이 값이 없을 수도 있다"는 사실을 코드 레벨에서 강제로 인지하게 만들고, 처리하도록 유도합니다. 런타임 에러가 아니라 작성 시점에 잡아주는 것입니다.
마무리
TypeScript는 JavaScript를 대체하는 새로운 언어가 아닙니다. JS 코드가 커지고 팀이 커질수록 발생하는 문제들을 해결하기 위한 도구입니다. 처음에는 타입을 하나하나 붙이는 게 번거롭게 느껴질 수 있습니다. 하지만 VS Code가 실시간으로 에러를 잡아주고, 자동완성이 정확해지고, 팀원의 코드가 자기 문서화되는 경험을 하고 나면, 타입 선언이 비용이 아니라 투자라는 것을 체감할 수 있습니다.