TypeScript란
TypeScript는 JavaScript에 정적 타입 시스템을 추가한 언어입니다. 컴파일 시점에 타입 오류를 잡아내므로, 런타임에서 발생할 수 있는 버그를 미리 방지할 수 있습니다. 대규모 프로젝트에서 코드의 안정성과 유지보수성을 높이는 데 핵심적인 역할을 합니다.
이 글에서는 TypeScript 설치, tsconfig.json 설정, 기본 타입, 함수 타입, 인터페이스까지 다룹니다.
설치와 프로젝트 초기화
Node.js가 설치된 환경에서 TypeScript를 전역 또는 프로젝트 단위로 설치할 수 있습니다.
# TypeScript 설치
npm init -y
npm install --save-dev typescript
# tsconfig.json 생성
npx tsc --init
생성된 tsconfig.json에서 핵심 옵션을 살펴봅시다.
{
"compilerOptions": {
// 컴파일 대상 JavaScript 버전
"target": "ES2022",
// 모듈 시스템
"module": "ESNext",
// 모듈 해석 전략
"moduleResolution": "bundler",
// 엄격 모드 활성화 (필수 권장)
"strict": true,
// 출력 디렉토리
"outDir": "./dist",
// 소스맵 생성 (디버깅용)
"sourceMap": true,
// .js 파일도 허용
"allowJs": true,
// ESModule interop
"esModuleInterop": true,
// 사용하지 않는 지역 변수 에러
"noUnusedLocals": true,
// 사용하지 않는 매개변수 에러
"noUnusedParameters": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
strict: true는 strictNullChecks, noImplicitAny, strictFunctionTypes 등 여러 옵션을 한꺼번에 활성화합니다. 새 프로젝트에서는 반드시 켜두는 것을 권장합니다.
기본 타입
TypeScript의 기본 타입은 JavaScript의 원시 타입에 대응합니다. 변수 선언 시 콜론(:) 뒤에 타입을 명시합니다.
// 기본 원시 타입
const name: string = "홍길동";
const age: number = 30;
const isActive: boolean = true;
// 배열 타입 (두 가지 표기법)
const scores: number[] = [95, 87, 92];
const names: Array<string> = ["김철수", "이영희"];
// 튜플: 고정 길이, 각 요소의 타입이 다름
const user: [string, number] = ["홍길동", 30];
// enum: 관련된 상수 그룹
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
const move: Direction = Direction.Up;
console.log(move); // "UP"
// any: 타입 검사 비활성화 (가급적 사용 금지)
let flexible: any = "문자열";
flexible = 42; // 에러 없음 — 타입 안전성 상실
// unknown: any보다 안전한 대안
let uncertain: unknown = "데이터";
// uncertain.toUpperCase(); // 에러! 타입 좁히기 필요
if (typeof uncertain === "string") {
console.log(uncertain.toUpperCase()); // "데이터" — 타입 좁히기 후 사용 가능
}
any와 unknown의 차이가 중요합니다. unknown은 사용 전에 반드시 타입을 좁혀야 하므로, 외부 데이터를 다룰 때 any 대신 unknown을 사용하는 것이 안전합니다.
함수 타입
함수의 매개변수와 반환 타입을 명시하면 호출 시 타입 불일치를 즉시 발견할 수 있습니다.
// 기본 함수 타입 정의
function add(a: number, b: number): number {
return a + b;
}
console.log(add(3, 5)); // 8
// 선택적 매개변수 (?)와 기본값
function greet(name: string, greeting: string = "안녕하세요"): string {
return `${greeting}, ${name}님!`;
}
console.log(greet("김철수")); // "안녕하세요, 김철수님!"
console.log(greet("이영희", "반갑습니다")); // "반갑습니다, 이영희님!"
// 나머지 매개변수
function sum(...numbers: number[]): number {
return numbers.reduce((acc, cur) => acc + cur, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
// 화살표 함수 타입
const multiply: (a: number, b: number) => number = (a, b) => a * b;
console.log(multiply(4, 7)); // 28
// void: 반환값 없는 함수
function logMessage(message: string): void {
console.log(`[LOG] ${message}`);
}
// never: 절대 반환하지 않는 함수 (예외 또는 무한 루프)
function throwError(message: string): never {
throw new Error(message);
}
인터페이스와 타입 별칭
객체의 구조를 정의할 때 interface와 type을 사용합니다. 둘 다 비슷하지만, interface는 선언 병합(declaration merging)을 지원하고 type은 유니온·교차 타입 등 더 유연한 조합이 가능합니다.
// interface: 객체 구조 정의
interface User {
id: number;
name: string;
email: string;
age?: number; // 선택적 속성
readonly createdAt: Date; // 읽기 전용
}
// 인터페이스를 사용한 객체 생성
const user: User = {
id: 1,
name: "홍길동",
email: "hong@example.com",
createdAt: new Date(),
};
// type 별칭: 유니온 타입
type Status = "active" | "inactive" | "pending";
// type 별칭: 객체 타입
type ApiResponse<T> = {
data: T;
status: number;
message: string;
};
// 인터페이스 확장
interface Admin extends User {
role: "admin" | "superadmin";
permissions: string[];
}
const admin: Admin = {
id: 2,
name: "관리자",
email: "admin@example.com",
createdAt: new Date(),
role: "admin",
permissions: ["read", "write", "delete"],
};
// 인터페이스 선언 병합
interface User {
nickname?: string; // 기존 User에 속성 추가됨
}
타입 좁히기 (Narrowing)
TypeScript는 조건문을 통해 타입을 자동으로 좁힙니다. 이를 **타입 가드(type guard)**라고 합니다.
// typeof 가드
function formatValue(value: string | number): string {
if (typeof value === "string") {
return value.toUpperCase(); // string으로 좁혀짐
}
return value.toFixed(2); // number로 좁혀짐
}
console.log(formatValue("hello")); // "HELLO"
console.log(formatValue(3.14159)); // "3.14"
// in 연산자 가드
interface Dog {
breed: string;
bark(): void;
}
interface Cat {
breed: string;
meow(): void;
}
function handlePet(pet: Dog | Cat): void {
if ("bark" in pet) {
pet.bark(); // Dog로 좁혀짐
} else {
pet.meow(); // Cat으로 좁혀짐
}
}
// 커스텀 타입 가드 (is 키워드)
function isString(value: unknown): value is string {
return typeof value === "string";
}
const data: unknown = "TypeScript";
if (isString(data)) {
console.log(data.length); // 10 — string으로 좁혀짐
}
정리
TypeScript를 시작할 때 기억해야 할 핵심 사항을 정리합니다.
- tsconfig.json에서
strict: true를 반드시 활성화합니다. 타입 안전성의 기본입니다. - 기본 타입(string, number, boolean, 배열, 튜플, enum)은 JavaScript의 원시 타입에 대응합니다.
- any 대신 unknown을 사용하고, 타입 좁히기를 통해 안전하게 접근합니다.
- 함수에는 매개변수 타입과 반환 타입을 명시합니다.
- interface는 객체 구조 정의와 확장에, type은 유니온·조합에 적합합니다.
- 타입 가드를 활용하면 런타임 안전성과 IDE 자동완성을 동시에 얻을 수 있습니다.