본문으로 건너뛰기

📘 타입스크립트 기초

📖 정의

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 기초를 이해했다면, 다음을 학습해보세요:

  1. React란? (문서 작성 예정) - TypeScript로 React 개발
  2. 번들러란? - TypeScript 빌드 환경
  3. TDD란? (문서 작성 예정) - TypeScript로 테스트 작성

🎬 마무리

TypeScript는 JavaScript를 더 안전하고 생산적으로 만들어줍니다:

  • 타입 안전성: 버그 사전 방지
  • 자동완성: 개발 속도 향상
  • 리팩토링: 안전한 코드 변경
  • 문서화: 코드가 곧 문서

대규모 프로젝트나 팀 협업에서 TypeScript는 필수입니다. 학습 곡선이 있지만, 투자할 가치가 충분합니다!