📘 타입스크립트 기초
📖 정의
TypeScript는 Microsoft가 개발한 JavaScript의 상위 집합(superset) 언어입니다. JavaScript에 정적 타입 시스템을 추가하여, 코드 작성 시점에 오류를 발견하고 개발 도구의 자동완성 기능을 향상시킵니다. TypeScript 코드는 컴파일을 통해 JavaScript로 변환되어 실 행됩니다.
🎯 비유로 이해하기
계약서 비유
TypeScript를 계약서에 비유하면:
JavaScript (구두 약속)
A: "내일 오후에 만나!"
B: "응, 알았어!"
다음날...
A: "왜 안 와?"
B: "오후 2시인 줄 알았는데?"
A: "아니, 오후 5시라고!"
문제:
- 구체적인 시간 명시 안 됨
- 오해 발생
- 실행 시점에 문제 발견
---
TypeScript (정식 계약서)
계약서:
- 날짜: 2024년 1월 15일
- 시간: 오후 5시 정각
- 장소: 강남역 2번 출구
- 참석자: A, B (타입 명시!)
장점:
- 명확한 약속
- 오해 불가능
- 계약서 작성 시점에 검증
- 법적 효력
공장 비유
JavaScript 공장 (검수 없음)
부품 입고:
- 어떤 부품이든 OK
- 크기 확인 안 함
- 규격 체크 안 함
조립 라인:
- 조립 중 문제 발견
- "이 나사가 안 맞네?"
- "이 부품 크기가 이상한데?"
결과:
- 불량품 발생
- 시간/비용 낭비
- 고객 불만
---
TypeScript 공장 (엄격한 검수)
부품 입고:
✓ 크기 측정
✓ 규격 확인
✓ 품질 검사
✗ 불량품 반려
조립 라인:
- 부품이 정확히 맞음
- 문제 없이 조립
- 빠른 생산
결과:
- 고품질 제품
- 효율적 생산
- 고객 만족
레고 블록 비유
JavaScript 레고
- 어떤 블록이든 끼워볼 수 있음
- 맞는지 안 맞는지는 끼워봐야 앎
- 큰 작품 만들 때 헷갈림
TypeScript 레고
- 블록마다 라벨
- "이 블록은 2x4 블록"
- "이 구멍엔 2x4만 들어감"
- 안 맞는 건 애초에 못 끼움
장점:
- 설명서 보고 정확히 조립
- 실수 방지
- 큰 작품도 문제없음
⚙️ 작동 원리
1. 정적 타입 시스템
// JavaScript (동적 타입)
let name = "김철수";
name = 123; // OK! 문제없음
name = true; // OK! 문제없음
name = null; // OK! 문제없음
// 실행 중에 문제 발생
function greet(person) {
return "Hello, " + person.name; // person이 뭔지 모름
}
greet({ name: "철수" }); // OK
greet("철수"); // 💥 Runtime Error!
greet(null); // 💥 Runtime Error!
// TypeScript (정적 타입)
let name: string = "김철수";
name = 123; // ❌ Compile Error! (타입 오류)
name = true; // ❌ Compile Error!
name = null; // ❌ Compile Error!
// 함수 매개변수와 반환 타입 명시
function greet(person: { name: string }): string {
return "Hello, " + person.name;
}
greet({ name: "철수" }); // ✅ OK
greet("철수"); // ❌ Compile Error! (타입 오류)
greet(null); // ❌ Compile Error!
// 장점:
// 1. 코드 작성 시점에 오류 발견
// 2. IDE 자동완성
// 3. 리팩토링 안전
// 4. 문서화 역할
2. 컴파일 과정
// TypeScript 코드
// app.ts
interface User {
id: number;
name: string;
email: string;
}
function getUser(id: number): User {
return {
id: id,
name: "김철수",
email: "kim@example.com"
};
}
const user: User = getUser(1);
console.log(user.name);
// ↓ TypeScript 컴파일러 (tsc)
// JavaScript 코드
// app.js
function getUser(id) {
return {
id: id,
name: "김철수",
email: "kim@example.com"
};
}
var user = getUser(1);
console.log(user.name);
// 특징:
// 1. 타입 정보 제거
// 2. ES5, ES6, ESNext 등으로 변환 가능
// 3. 브라우저/Node.js에서 실행
// 4. 소스맵 생성 (디버깅용)
3. 타입 추론
// TypeScript는 타입을 자동으로 추론
// 명시적 타입
let name: string = "김철수";
let age: number = 25;
// 타입 추론 (자동)
let name = "김철수"; // string으로 추론
let age = 25; // number로 추론
let isActive = true; // boolean으로 추론
// 배열
let numbers = [1, 2, 3]; // number[]로 추론
let names = ["김", "이"]; // string[]로 추론
// 객체
let user = {
name: "김철수",
age: 25
};
// { name: string; age: number }로 추론
// 함수 반환 타입
function add(a: number, b: number) {
return a + b; // number 반환으로 추론
}
// 장점:
// - 타입 작성 줄임
// - 여전히 타입 안전성
// - 가독성 향상
💡 실제 예시
기본 타입
// 1. 원시 타입
let name: string = "김철수";
let age: number = 25;
let isStudent: boolean = true;
let nothing: null = null;
let notDefined: undefined = undefined;
// 2. 배열
let numbers: number[] = [1, 2, 3, 4, 5];
let names: Array<string> = ["김철수", "이영희"];
// 혼합 배열
let mixed: (number | string)[] = [1, "two", 3, "four"];
// 3. 튜플 (고정 길이, 각 위치마다 타입)
let person: [string, number] = ["김철수", 25];
let coordinate: [number, number] = [10, 20];
// ❌ 잘못된 사용
person = [25, "김철수"]; // 순서 틀림
person = ["김철수"]; // 길이 틀림
person = ["김철수", 25, true]; // 길이 틀림
// 4. enum (열거형)
enum Color {
Red, // 0
Green, // 1
Blue // 2
}
let color: Color = Color.Red;
console.log(color); // 0
// 값 지정
enum Status {
Pending = "PENDING",
Approved = "APPROVED",
Rejected = "REJECTED"
}
let status: Status = Status.Pending;
console.log(status); // "PENDING"
// 5. any (모든 타입 허용)
let anything: any = "문자열";
anything = 123; // OK
anything = true; // OK
anything = null; // OK
// ⚠️ any는 TypeScript의 장점을 없앰!
// 꼭 필요한 경우만 사용
// 6. unknown (any보다 안전)
let value: unknown = "문자열";
// ❌ 바로 사용 불가
// console.log(value.length);
// ✅ 타입 체크 후 사용
if (typeof value === "string") {
console.log(value.length); // OK
}
// 7. void (반환값 없음)
function logMessage(message: string): void {
console.log(message);
// return 없음
}
// 8. never (절대 반환 안 함)
function throwError(message: string): never {
throw new Error(message);
// 함수가 절대 끝나지 않음
}
function infiniteLoop(): never {
while (true) {
// 무한 루프
}
}
// 9. object
let obj: object = { name: "김철수" };
let arr: object = [1, 2, 3];
let func: object = function() {};
인터페이스
// Interface: 객체의 구조 정의
// 1. 기본 인터페이스
interface User {
id: number;
name: string;
email: string;
}
const user: User = {
id: 1,
name: "김철수",
email: "kim@example.com"
};
// ❌ 속성 누락
const invalidUser: User = {
id: 1,
name: "김철수"
// email 없음!
};
// 2. 선택적 속성
interface Product {
id: number;
name: string;
price: number;
description?: string; // 선택적 (있어도 되고 없어도 됨)
}
const product1: Product = {
id: 1,
name: "노트북",
price: 1000000,
description: "고성능 노트북"
};
const product2: Product = {
id: 2,
name: "마우스",
price: 30000
// description 없어도 OK
};
// 3. 읽기 전용 속성
interface Config {
readonly apiUrl: string;
readonly timeout: number;
}
const config: Config = {
apiUrl: "https://api.example.com",
timeout: 5000
};
// ❌ 수정 불가
config.apiUrl = "https://api2.example.com";
// 4. 함수 타입
interface SearchFunc {
(query: string, page: number): string[];
}
const search: SearchFunc = (query, page) => {
// 구현
return ["결과1", "결과2"];
};
// 5. 인덱스 시그니처 (동적 속성)
interface StringMap {
[key: string]: string;
}
const translations: StringMap = {
hello: "안녕하세요",
goodbye: "안녕히 가세요",
thanks: "감사합니다"
// 키-값 모두 string
};
// 6. 인터페이스 확장 (상속)
interface Person {
name: string;
age: number;
}
interface Student extends Person {
studentId: string;
grade: number;
}
const student: Student = {
name: "김철수",
age: 20,
studentId: "2024001",
grade: 2
};
// 7. 여러 인터페이스 확장
interface Printable {
print(): void;
}
interface Scannable {
scan(): void;
}
interface Printer extends Printable, Scannable {
model: string;
}
const printer: Printer = {
model: "HP-1234",
print() {
console.log("프린트 중...");
},
scan() {
console.log("스캔 중...");
}
};
Type Alias
// Type Alias: 타 입에 이름 붙이기
// 1. 기본 타입 별칭
type UserID = number;
type UserName = string;
let id: UserID = 123;
let name: UserName = "김철수";
// 2. 유니온 타입
type Status = "pending" | "approved" | "rejected";
let orderStatus: Status = "pending";
orderStatus = "approved"; // OK
// orderStatus = "shipped"; // ❌ Error
// 3. 객체 타입
type Point = {
x: number;
y: number;
};
const point: Point = { x: 10, y: 20 };
// 4. 함수 타입
type GreetFunction = (name: string) => string;
const greet: GreetFunction = (name) => {
return `Hello, ${name}!`;
};
// 5. 인터섹션 타입 (교집합)
type Person = {
name: string;
age: number;
};
type Employee = {
employeeId: string;
department: string;
};
type EmployeePerson = Person & Employee;
const employee: EmployeePerson = {
name: "김철수",
age: 30,
employeeId: "E123",
department: "개발팀"
// 모든 속성 필요!
};
// 6. Interface vs Type
// Interface (확장 가능)
interface Animal {
name: string;
}
interface Animal {
age: number; // 병합됨!
}
const animal: Animal = {
name: "강아지",
age: 3
};
// Type (확장 불가)
type Car = {
model: string;
};
// ❌ Error: 중복 선언 불가
// type Car = {
// year: number;
// };
// 언제 뭘 쓸까?
// - Interface: 객체 구조, 클래스, 확장 가능
// - Type: 유니온, 인터섹션, 유틸리티 타입
제네릭 (Generics)
// 제네릭: 재사용 가능한 타입
// ❌ 제네릭 없이 (중복 코드)
function numberIdentity(value: number): number {
return value;
}
function stringIdentity(value: string): string {
return value;
}
function booleanIdentity(value: boolean): boolean {
return value;
}
// ✅ 제네릭 사용 (하나로 통합)
function identity<T>(value: T): T {
return value;
}
const num = identity<number>(123); // number
const str = identity<string>("hello"); // string
const bool = identity<boolean>(true); // boolean
// 타입 추론으로 더 간단히
const num2 = identity(123); // number로 추론
const str2 = identity("hello"); // string으로 추론
// 2. 제네릭 배열
function getFirstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
const firstNum = getFirstElement([1, 2, 3]); // number
const firstName = getFirstElement(["a", "b", "c"]); // string
// 3. 제네릭 인터페이스
interface Box<T> {
value: T;
}
const numberBox: Box<number> = { value: 123 };
const stringBox: Box<string> = { value: "hello" };
// 4. 제네릭 클래스
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.push(3);
console.log(numberStack.pop()); // 3
const stringStack = new Stack<string>();
stringStack.push("a");
stringStack.push("b");
// 5. 제네릭 제약 (Constraints)
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): void {
console.log(item.length);
}
logLength("hello"); // OK (string has length)
logLength([1, 2, 3]); // OK (array has length)
// logLength(123); // ❌ Error (number doesn't have length)
// 6. 여러 제네릭 타입
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const p1 = pair<string, number>("age", 25);
const p2 = pair("name", "김철수"); // 타입 추론
// 7. 제네릭 기본값
interface Container<T = string> {
value: T;
}
const container1: Container = { value: "hello" }; // string
const container2: Container<number> = { value: 123 };
유틸리티 타입
// TypeScript 내장 유틸리티 타입
// 1. Partial<T> (모든 속성 선택적으로)
interface User {
id: number;
name: string;
email: string;
}
function updateUser(id: number, updates: Partial<User>): void {
// updates는 일부 속성만 있어도 OK
}
updateUser(1, { name: "김철수" }); // OK
updateUser(2, { email: "new@example.com" }); // OK
updateUser(3, { name: "이영희", email: "lee@example.com" }); // OK
// 2. Required<T> (모든 속성 필수로)
interface PartialUser {
id?: number;
name?: string;
email?: string;
}
type RequiredUser = Required<PartialUser>;
const user: RequiredUser = {
id: 1,
name: "김철수",
email: "kim@example.com"
// 모든 속성 필수!
};
// 3. Readonly<T> (모든 속성 읽기 전용)
interface Config {
apiUrl: string;
timeout: number;
}
const config: Readonly<Config> = {
apiUrl: "https://api.example.com",
timeout: 5000
};
// ❌ 수정 불가
// config.apiUrl = "https://api2.example.com";
// 4. Pick<T, K> (특정 속성만 선택)
interface Product {
id: number;
name: string;
price: number;
description: string;
stock: number;
}
type ProductPreview = Pick<Product, "id" | "name" | "price">;
const preview: ProductPreview = {
id: 1,
name: "노트북",
price: 1000000
// description, stock 불필요
};
// 5. Omit<T, K> (특정 속성 제외)
type ProductWithoutStock = Omit<Product, "stock">;
const product: ProductWithoutStock = {
id: 1,
name: "노트북",
price: 1000000,
description: "고성능 노트북"
// stock 제외됨
};
// 6. Record<K, T> (키-값 매핑)
type PageInfo = Record<string, { title: string; url: string }>;
const pages: PageInfo = {
home: { title: "홈", url: "/" },
about: { title: "소개", url: "/about" },
contact: { title: "연락처", url: "/contact" }
};
// 7. Exclude<T, U> (유니온에서 타입 제외)
type AllStatus = "pending" | "approved" | "rejected" | "draft";
type ActiveStatus = Exclude<AllStatus, "draft">;
// "pending" | "approved" | "rejected"
// 8. Extract<T, U> (유니온에서 타입 추출)
type Status = "pending" | "approved" | "rejected";
type PositiveStatus = Extract<Status, "approved">;
// "approved"
// 9. NonNullable<T> (null, undefined 제거)
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;
// string
// 10. ReturnType<T> (함수 반환 타입 추출)
function getUser() {
return {
id: 1,
name: "김철수",
email: "kim@example.com"
};
}
type User = ReturnType<typeof getUser>;
// { id: number; name: string; email: string }
JavaScript → TypeScript 변환
// JavaScript 코드
// user.js
function createUser(name, age, email) {
return {
name: name,
age: age,
email: email,
greet: function() {
return "Hello, " + this.name + "!";
}
};
}
function getUsers() {
return fetch('/api/users')
.then(res => res.json())
.then(data => data.users);
}
const user = createUser("김철수", 25, "kim@example.com");
console.log(user.greet());
// ↓ TypeScript로 변환
// user.ts
interface User {
name: string;
age: number;
email: string;
greet(): string;
}
function createUser(name: string, age: number, email: string): User {
return {
name,
age,
email,
greet() {
return `Hello, ${this.name}!`;
}
};
}
interface ApiResponse {
users: User[];
}
async function getUsers(): Promise<User[]> {
const response = await fetch('/api/users');
const data: ApiResponse = await response.json();
return data.users;
}
const user: User = createUser("김철수", 25, "kim@example.com");
console.log(user.greet());
// 장점:
// 1. 타입 안전성
// 2. IDE 자동완성
// 3. 리팩토링 쉬움
// 4. 버그 사전 방지
React에서 TypeScript
// React 컴포넌트 (JavaScript)
// Button.jsx
function Button({ label, onClick, disabled }) {
return (
<button onClick={onClick} disabled={disabled}>
{label}
</button>
);
}
// ↓ TypeScript로 변환
// Button.tsx
interface ButtonProps {
label: string;
onClick: () => void;
disabled?: boolean;
}
function Button({ label, onClick, disabled = false }: ButtonProps) {
return (
<button onClick={onClick} disabled={disabled}>
{label}
</button>
);
}
// 또는 React.FC 사용
const Button: React.FC<ButtonProps> = ({ label, onClick, disabled = false }) => {
return (
<button onClick={onClick} disabled={disabled}>
{label}
</button>
);
};
// 사용
<Button
label="클릭"
onClick={() => console.log('clicked')}
disabled={false}
/>
// ❌ 잘못된 사용
<Button
label={123} // ❌ Error: label은 string
onClick="handleClick" // ❌ Error: onClick은 함수
/>
// 2. State와 Hooks
import { useState, useEffect } from 'react';
interface User {
id: number;
name: string;
email: string;
}
function UserProfile() {
// State 타입 지정
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
setLoading(true);
fetch('/api/user')
.then(res => res.json())
.then((data: User) => {
setUser(data);
setLoading(false);
})
.catch((err: Error) => {
setError(err.message);
setLoading(false);
});
}, []);
if (loading) return <div>로딩 중...</div>;
if (error) return <div>에러: {error}</div>;
if (!user) return <div>사용자 없음</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
// 3. Event Handler
interface FormProps {
onSubmit: (data: { name: string; email: string }) => void;
}
function Form({ onSubmit }: FormProps) {
const [name, setName] = useState<string>('');
const [email, setEmail] = useState<string>('');
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
onSubmit({ name, email });
};
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={handleNameChange}
/>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button type="submit">제출</button>
</form>
);
}
일반적인 에러와 해결
// 1. Object is possibly 'null' or 'undefined'
// ❌ Error
function getLength(text: string | null) {
return text.length; // ❌ text가 null일 수 있음
}
// ✅ 해결 1: null 체크
function getLength(text: string | null) {
if (text === null) {
return 0;
}
return text.length; // ✅ OK
}
// ✅ 해결 2: Optional Chaining
function getLength(text: string | null) {
return text?.length ?? 0;
}
// 2. Type 'X' is not assignable to type 'Y'
// ❌ Error
interface User {
name: string;
age: number;
}
const user: User = {
name: "김철수"
// age 누락!
};
// ✅ 해결: 모든 속성 추가
const user: User = {
name: "김철수",
age: 25
};
// 3. Cannot find name 'X'
// ❌ Error
console.log(process.env.API_URL); // ❌ process 타입 없음
// ✅ 해결: @types 설치
// npm install --save-dev @types/node
// 4. Property 'X' does not exist on type 'Y'
// ❌ Error
const obj = {};
obj.name = "김철수"; // ❌ name 속성 없음
// ✅ 해결 1: 타입 정의
interface Obj {
name: string;
}
const obj: Obj = { name: "" };
obj.name = "김철수"; // ✅ OK
// ✅ 해결 2: 인덱스 시그니처
const obj: { [key: string]: string } = {};
obj.name = "김철수"; // ✅ OK
// 5. Type assertions (타입 단언)
const input = document.getElementById('myInput');
// input은 HTMLElement | null
// ❌ Error
input.value = "hello"; // HTMLElement에 value 없음
// ✅ 해결: 타입 단언
const input = document.getElementById('myInput') as HTMLInputElement;
input.value = "hello"; // ✅ OK
// 또는
const input = <HTMLInputElement>document.getElementById('myInput');
input.value = "hello"; // ✅ OK
// 6. Non-null assertion operator (!)
const element = document.getElementById('app');
// element는 HTMLElement | null
// ❌ Error
element.innerHTML = "hello"; // null일 수 있음
// ✅ 해결: null 체크
if (element) {
element.innerHTML = "hello";
}
// 또는 ! 연산자 (확실할 때만!)
element!.innerHTML = "hello"; // null이 아니라고 단언
// 7. Type narrowing (타입 좁히기)
function printValue(value: string | number) {
// ❌ Error
// console.log(value.toUpperCase()); // number에는 toUpperCase 없음
// ✅ 해결: typeof로 타입 체크
if (typeof value === "string") {
console.log(value.toUpperCase()); // ✅ OK (string)
} else {
console.log(value.toFixed(2)); // ✅ OK (number)
}
}
🤔 자주 묻는 질문
Q1. TypeScript를 왜 사용해야 하나요?
A: 여러 가지 이유가 있습니다:
// 1. 버그 사전 방지
// JavaScript
function calculateTotal(items) {
let total = 0;
items.forEach(item => {
total += item.price;
});
return total;
}
// 실행 중 발견되는 버그:
calculateTotal(null); // 💥 Runtime Error!
calculateTotal([{ name: "상품" }]); // 💥 price가 undefined
// TypeScript
interface Item {
name: string;
price: number;
}
function calculateTotal(items: Item[]): number {
let total = 0;
items.forEach(item => {
total += item.price;
});
return total;
}
// 코드 작성 중 발견:
calculateTotal(null); // ❌ Compile Error!
calculateTotal([{ name: "상품" }]); // ❌ Compile Error! (price 없음)
// 2. 자동완성과 IntelliSense
const user = {
name: "김철수",
age: 25,
email: "kim@example.com"
};
// JavaScript: user. 입력 시 자동완성 없음
// TypeScript: user. 입력 시 name, age, email 자동완성!
// 3. 리팩토링 안전
// 함수 시그니처 변경 시
function greet(name) {
return `Hello, ${name}!`;
}
// TypeScript는 모든 호출 위치를 찾아서 에러 표시
// JavaScript는 실행해봐야 문제 발견
// 4. 문서화 역할
// JavaScript
function createUser(name, age, email, isAdmin) {
// name이 string인지 number인지 모름
// age가 필수인지 선택적인지 모름
// isAdmin이 뭔지 모름
}
// TypeScript
interface CreateUserParams {
name: string;
age: number;
email: string;
isAdmin?: boolean; // 선택적, 기본값 false
}
function createUser(params: CreateUserParams): User {
// 모든 게 명확!
}
// 5. 팀 협업
// TypeScript 코드를 보면:
// - 함수가 어떤 인자를 받는지
// - 어떤 타입을 반환하는지
// - 어떤 속성이 있는지
// 모두 명확!
// 결론:
// JavaScript: 빠른 프로토타입, 간단한 스크립트
// TypeScript: 대규모 프로젝트, 팀 협업, 유지보수
Q2. TypeScript의 단점은 없나요?
A: 단점도 있습니다:
// 단점 1: 학습 곡선
// JavaScript만 알면 바로 시작 가능
// TypeScript는 타입 시스템 학습 필요
// 단점 2: 초기 설정
// JavaScript: 파일 만들고 바로 실행
// TypeScript: 설정 파일, 컴파일 필요
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
// ... 많은 옵션
}
}
// 단점 3: 타입 작성 시간
// JavaScript
const users = [
{ name: "김철수", age: 25 },
{ name: "이영희", age: 30 }
];
// TypeScript (타입 정의 필요)
interface User {
name: string;
age: number;
}
const users: User[] = [
{ name: "김철수", age: 25 },
{ name: "이영희", age: 30 }
];
// 단점 4: 서드파티 라이브러리
// 타입 정의 없는 라이브러리는 any 사용 필요
import someLib from 'old-library'; // 타입 없음
// @types/old-library 설치 필요
// 단점 5: 컴파일 시간
// 큰 프로젝트는 컴파일 시간 소요
// (하지만 esbuild, SWC로 많이 개선됨)
// 하지만 장점이 단점보다 훨씬 큼!
// 특히 대규모 프로젝트에서는 필수!
Q3. any를 사용해도 되나요?
A: 최대한 피하는 것이 좋습니다:
// ❌ any 남용 (TypeScript 장점 잃음)
function processData(data: any) {
return data.map((item: any) => {
return {
id: item.id,
value: item.value
};
});
}
// ✅ 적절한 타입 사용
interface DataItem {
id: number;
value: string;
}
function processData(data: DataItem[]) {
return data.map(item => {
return {
id: item.id,
value: item.value
};
});
}
// any를 써도 되는 경우:
// 1. 서드파티 라이브러리 (타입 정의 없음)
import oldLib from 'very-old-library';
const result: any = oldLib.doSomething();
// 2. JSON 파싱 (구조 모름)
const data: any = JSON.parse(jsonString);
// 이후 타입 가드로 검증
// 3. 레거시 코드 마이그레이션
// JavaScript → TypeScript 점진적 전환 시
// 대안:
// - unknown: any보다 안전
// - 제네릭: 타입 보존
// - 타입 가드: 런타임 검증
Q4. interface vs type, 언제 뭘 쓰나요?
A: 상황에 따라 다릅니다:
// Interface 사용 (권장)
// 1. 객체 구조 정의
interface User {
id: number;
name: string;
}
// 2. 확장 가능
interface Student extends User {
studentId: string;
}
// 3. 선언 병합 (Declaration Merging)
interface Window {
myCustomProperty: string;
}
interface Window {
anotherProperty: number;
}
// 두 선언이 병합됨!
// Type 사용
// 1. 유니온 타입
type Status = "pending" | "approved" | "rejected";
// 2. 인터섹션 타입
type Employee = Person & Worker;
// 3. 함수 타입
type GreetFunction = (name: string) => string;
// 4. 유틸리티 타입
type PartialUser = Partial<User>;
type PickedUser = Pick<User, "id" | "name">;
// 5. 튜플
type Point = [number, number];
// 6. Mapped Types
type ReadonlyUser = {
readonly [K in keyof User]: User[K];
};
// 추천:
// - 기본적으로 interface 사용
// - 유니온/인터섹션 필요하면 type 사용
// - 일관성 유지 (프로젝트 내)
Q5. TypeScript 프로젝트를 어떻게 시작하나요?
A: 여러 방법이 있습니다:
# 1. Create React App (TypeScript 템플릿)
npx create-react-app my-app --template typescript
# 2. Next.js (TypeScript)
npx create-next-app@latest my-app --typescript
# 3. Vite (TypeScript)
npm create vite@latest my-app -- --template react-ts
# 4. 기존 프로젝트에 TypeScript 추가
npm install --save-dev typescript @types/react @types/react-dom
# tsconfig.json 생성
npx tsc --init
# 5. 순수 TypeScript 프로젝트
mkdir my-project
cd my-project
npm init -y
npm install --save-dev typescript @types/node
# tsconfig.json 생성
npx tsc --init
# src/index.ts 파일 생성
echo 'console.log("Hello TypeScript!");' > src/index.ts
# 컴파일
npx tsc
# 실행
node dist/index.js
// tsconfig.json (추천 설정)
{
"compilerOptions": {
// 타겟 JavaScript 버전
"target": "ES2020",
// 모듈 시스템
"module": "ESNext",
"moduleResolution": "node",
// JSX 지원 (React)
"jsx": "react-jsx",
// 엄격한 타입 체크
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
// 모듈 해석
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
// 출력 디렉토리
"outDir": "./dist",
// 소스맵 생성
"sourceMap": true,
// 불필요한 체크 스킵
"skipLibCheck": true,
// 경로 별칭
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
🎓 다음 단계
TypeScript 기초를 이해했다면, 다음을 학습해보세요:
- React란? (문서 작성 예정) - TypeScript로 React 개발
- 번들러란? - TypeScript 빌드 환경
- TDD란? (문서 작성 예정) - TypeScript로 테스트 작성
🎬 마무리
TypeScript는 JavaScript를 더 안전하고 생산적으로 만들어줍니다:
- 타입 안전성: 버그 사전 방지
- 자동완성: 개발 속도 향상
- 리팩토링: 안전한 코드 변경
- 문서화: 코드가 곧 문서
대규모 프로젝트나 팀 협업에서 TypeScript는 필수입니다. 학습 곡선이 있지만, 투자할 가치가 충분합니다!