10. TypeScript 기초 (TypeScript Fundamentals)

10. TypeScript 기초 (TypeScript Fundamentals)

ν•™μŠ΅ λͺ©ν‘œ

  • TypeScript의 μž₯점과 JavaScriptμ™€μ˜ 관계 이해
  • κΈ°λ³Έ νƒ€μž… μ‹œμŠ€ν…œ λ§ˆμŠ€ν„°
  • μΈν„°νŽ˜μ΄μŠ€μ™€ νƒ€μž… 별칭 ν™œμš©
  • μ œλ„€λ¦­μ„ ν†΅ν•œ μž¬μ‚¬μš© κ°€λŠ₯ν•œ μ½”λ“œ μž‘μ„±
  • μœ ν‹Έλ¦¬ν‹° νƒ€μž…κ³Ό κ³ κΈ‰ νƒ€μž… κΈ°λŠ₯ 이해

λͺ©μ°¨

  1. TypeScript μ†Œκ°œ
  2. κΈ°λ³Έ νƒ€μž…
  3. μΈν„°νŽ˜μ΄μŠ€μ™€ νƒ€μž…
  4. ν•¨μˆ˜ νƒ€μž…
  5. μ œλ„€λ¦­
  6. μœ ν‹Έλ¦¬ν‹° νƒ€μž…
  7. μ—°μŠ΅ 문제

1. TypeScript μ†Œκ°œ

1.1 TypeScriptλž€?

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    TypeScript κ°œμš”                               β”‚
β”‚                                                                 β”‚
β”‚   TypeScript = JavaScript + 정적 νƒ€μž…                           β”‚
β”‚                                                                 β”‚
β”‚   νŠΉμ§•:                                                         β”‚
β”‚   - Microsoftμ—μ„œ 개발                                          β”‚
β”‚   - JavaScript의 μƒμœ„ μ§‘ν•© (Superset)                           β”‚
β”‚   - 컴파일 μ‹œ νƒ€μž… 검사                                          β”‚
β”‚   - λͺ¨λ“  JavaScript μ½”λ“œλŠ” μœ νš¨ν•œ TypeScript                    β”‚
β”‚                                                                 β”‚
β”‚   μž₯점:                                                         β”‚
β”‚   - λŸ°νƒ€μž„ μ „ 였λ₯˜ 발견                                          β”‚
β”‚   - IDE 지원 ν–₯상 (μžλ™μ™„μ„±, λ¦¬νŒ©ν† λ§)                           β”‚
β”‚   - μ½”λ“œ 가독성 및 λ¬Έμ„œν™”                                        β”‚
β”‚   - λŒ€κ·œλͺ¨ ν”„λ‘œμ νŠΈ μœ μ§€λ³΄μˆ˜ 용이                                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

1.2 μ„€μΉ˜ 및 μ„€μ •

# TypeScript μ„€μΉ˜
npm install -g typescript

# 버전 확인
tsc --version

# ν”„λ‘œμ νŠΈ μ΄ˆκΈ°ν™”
npm init -y
npm install typescript --save-dev

# tsconfig.json 생성
npx tsc --init
// tsconfig.json κΈ°λ³Έ μ„€μ •
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "moduleResolution": "node"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

1.3 컴파일과 μ‹€ν–‰

# 단일 파일 컴파일
tsc hello.ts

# ν”„λ‘œμ νŠΈ 전체 컴파일
tsc

# κ°μ‹œ λͺ¨λ“œ (파일 λ³€κ²½ μ‹œ μžλ™ 컴파일)
tsc --watch

# ts-node둜 직접 μ‹€ν–‰ (개발용)
npm install -g ts-node
ts-node hello.ts

2. κΈ°λ³Έ νƒ€μž…

2.1 μ›μ‹œ νƒ€μž…

// λ¬Έμžμ—΄
let name: string = "TypeScript";
let greeting: string = `Hello, ${name}!`;

// 숫자
let age: number = 25;
let price: number = 99.99;
let hex: number = 0xf00d;

// λΆˆλ¦¬μ–Έ
let isActive: boolean = true;
let hasError: boolean = false;

// nullκ³Ό undefined
let nothing: null = null;
let notDefined: undefined = undefined;

// BigInt (ES2020+)
let bigNumber: bigint = 9007199254740991n;

// Symbol
let sym: symbol = Symbol("unique");

2.2 λ°°μ—΄κ³Ό νŠœν”Œ

// λ°°μ—΄ νƒ€μž… (두 κ°€μ§€ 방식)
let numbers: number[] = [1, 2, 3, 4, 5];
let strings: Array<string> = ["a", "b", "c"];

// 닀차원 λ°°μ—΄
let matrix: number[][] = [
  [1, 2, 3],
  [4, 5, 6],
];

// νŠœν”Œ (κ³ μ • 길이, κ³ μ • νƒ€μž… λ°°μ—΄)
let tuple: [string, number] = ["Alice", 30];
let rgb: [number, number, number] = [255, 128, 0];

// νŠœν”Œ μš”μ†Œ μ ‘κ·Ό
const [userName, userAge] = tuple;
console.log(userName); // "Alice"

// λͺ…λͺ…λœ νŠœν”Œ (가독성 ν–₯상)
type Point = [x: number, y: number];
const point: Point = [10, 20];

2.3 객체 νƒ€μž…

// κΈ°λ³Έ 객체 νƒ€μž…
let person: { name: string; age: number } = {
  name: "Bob",
  age: 25,
};

// 선택적 속성 (?)
let config: { host: string; port?: number } = {
  host: "localhost",
  // portλŠ” 선택적
};

// 읽기 μ „μš© 속성
let user: { readonly id: number; name: string } = {
  id: 1,
  name: "Alice",
};
// user.id = 2;  // 였λ₯˜! readonly

// 인덱슀 μ‹œκ·Έλ‹ˆμ²˜
let dictionary: { [key: string]: number } = {
  apple: 1,
  banana: 2,
};

2.4 특수 νƒ€μž…

// any - λͺ¨λ“  νƒ€μž… ν—ˆμš© (μ‚¬μš© 자제)
let anything: any = "hello";
anything = 42;
anything = { foo: "bar" };

// unknown - any보닀 μ•ˆμ „ν•œ λŒ€μ•ˆ
let unknownValue: unknown = "hello";
// unknownValue.toUpperCase();  // 였λ₯˜!
if (typeof unknownValue === "string") {
  unknownValue.toUpperCase(); // OK - νƒ€μž… κ°€λ“œ ν›„
}

// void - λ°˜ν™˜κ°’ μ—†μŒ
function logMessage(msg: string): void {
  console.log(msg);
}

// never - μ ˆλŒ€ λ°˜ν™˜ν•˜μ§€ μ•ŠμŒ
function throwError(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {}
}

2.5 Unionκ³Ό Intersection

// Union νƒ€μž… (|) - μ—¬λŸ¬ νƒ€μž… 쀑 ν•˜λ‚˜
let id: string | number;
id = "abc";
id = 123;

type Status = "pending" | "approved" | "rejected";
let orderStatus: Status = "pending";

// Intersection νƒ€μž… (&) - λͺ¨λ“  νƒ€μž… κ²°ν•©
type Name = { name: string };
type Age = { age: number };
type Person = Name & Age;

const person: Person = {
  name: "Alice",
  age: 30,
};

2.6 νƒ€μž… μΆ”λ‘ κ³Ό νƒ€μž… 단언

// νƒ€μž… μΆ”λ‘  - TypeScriptκ°€ μžλ™μœΌλ‘œ νƒ€μž… κ²°μ •
let message = "Hello"; // string으둜 μΆ”λ‘ 
let count = 10; // number둜 μΆ”λ‘ 

// νƒ€μž… 단언 (Type Assertion)
let someValue: unknown = "this is a string";

// 방법 1: as 문법 (ꢌμž₯)
let strLength1: number = (someValue as string).length;

// 방법 2: angle-bracket 문법 (JSX와 좩돌)
let strLength2: number = (<string>someValue).length;

// const 단언
let colors = ["red", "green", "blue"] as const;
// readonly ["red", "green", "blue"] νƒ€μž…

// Non-null 단언 (!)
function getLength(str: string | null): number {
  return str!.length; // null이 μ•„λ‹˜μ„ 단언
}

3. μΈν„°νŽ˜μ΄μŠ€μ™€ νƒ€μž…

3.1 μΈν„°νŽ˜μ΄μŠ€ κΈ°λ³Έ

// μΈν„°νŽ˜μ΄μŠ€ μ •μ˜
interface User {
  id: number;
  name: string;
  email: string;
  age?: number; // 선택적
  readonly createdAt: Date; // 읽기 μ „μš©
}

// μΈν„°νŽ˜μ΄μŠ€ μ‚¬μš©
const user: User = {
  id: 1,
  name: "Alice",
  email: "alice@example.com",
  createdAt: new Date(),
};

// ν•¨μˆ˜ νƒ€μž… μΈν„°νŽ˜μ΄μŠ€
interface Calculator {
  (a: number, b: number): number;
}

const add: Calculator = (a, b) => a + b;

3.2 μΈν„°νŽ˜μ΄μŠ€ ν™•μž₯

// μΈν„°νŽ˜μ΄μŠ€ 상속
interface Animal {
  name: string;
  age: number;
}

interface Dog extends Animal {
  breed: string;
  bark(): void;
}

const myDog: Dog = {
  name: "Buddy",
  age: 3,
  breed: "Labrador",
  bark() {
    console.log("Woof!");
  },
};

// 닀쀑 상속
interface Pet extends Animal {
  owner: string;
}

interface ServiceDog extends Dog, Pet {
  certificationId: string;
}

3.3 νƒ€μž… 별칭 (Type Alias)

// νƒ€μž… 별칭 μ •μ˜
type ID = string | number;
type Point = { x: number; y: number };
type Callback = (data: string) => void;

// μ‚¬μš©
let userId: ID = "user_123";
let position: Point = { x: 10, y: 20 };

// μœ λ‹ˆμ˜¨ νƒ€μž…μ— 유용
type Result<T> = { success: true; data: T } | { success: false; error: string };

function fetchData(): Result<User> {
  return { success: true, data: { id: 1, name: "Alice", email: "a@b.com", createdAt: new Date() } };
}

3.4 μΈν„°νŽ˜μ΄μŠ€ vs νƒ€μž…

// μΈν„°νŽ˜μ΄μŠ€ - μ„ μ–Έ 병합 κ°€λŠ₯
interface Window {
  title: string;
}

interface Window {
  size: number; // μžλ™ 병합됨
}

// νƒ€μž… - 병합 λΆˆκ°€, 더 μœ μ—°
type StringOrNumber = string | number; // μœ λ‹ˆμ˜¨
type Point = [number, number]; // νŠœν”Œ

// ꢌμž₯사항:
// - 객체 ν˜•νƒœ μ •μ˜: interface μ‚¬μš©
// - μœ λ‹ˆμ˜¨, νŠœν”Œ, μ›μ‹œ νƒ€μž… 별칭: type μ‚¬μš©
// - 라이브러리 API: interface (ν™•μž₯ κ°€λŠ₯)

4. ν•¨μˆ˜ νƒ€μž…

4.1 ν•¨μˆ˜ νƒ€μž… μ •μ˜

// ν•¨μˆ˜ μ„ μ–Έ
function add(a: number, b: number): number {
  return a + b;
}

// ν™”μ‚΄ν‘œ ν•¨μˆ˜
const multiply = (a: number, b: number): number => a * b;

// ν•¨μˆ˜ νƒ€μž… 별칭
type MathOperation = (a: number, b: number) => number;

const divide: MathOperation = (a, b) => a / b;

// ν•¨μˆ˜ νƒ€μž… μΈν„°νŽ˜μ΄μŠ€
interface MathFunc {
  (a: number, b: number): number;
  description?: string;
}

4.2 λ§€κ°œλ³€μˆ˜ μ˜΅μ…˜

// 선택적 λ§€κ°œλ³€μˆ˜ (?)
function greet(name: string, greeting?: string): string {
  return `${greeting || "Hello"}, ${name}!`;
}

// κΈ°λ³Έκ°’ λ§€κ°œλ³€μˆ˜
function greetWithDefault(name: string, greeting: string = "Hello"): string {
  return `${greeting}, ${name}!`;
}

// λ‚˜λ¨Έμ§€ λ§€κ°œλ³€μˆ˜
function sum(...numbers: number[]): number {
  return numbers.reduce((acc, n) => acc + n, 0);
}

console.log(sum(1, 2, 3, 4, 5)); // 15

4.3 ν•¨μˆ˜ μ˜€λ²„λ‘œλ”©

// ν•¨μˆ˜ μ˜€λ²„λ‘œλ”© μ‹œκ·Έλ‹ˆμ²˜
function process(x: string): string;
function process(x: number): number;
function process(x: string | number): string | number {
  if (typeof x === "string") {
    return x.toUpperCase();
  }
  return x * 2;
}

console.log(process("hello")); // "HELLO"
console.log(process(5)); // 10

4.4 this νƒ€μž…

interface Button {
  label: string;
  click(this: Button): void;
}

const button: Button = {
  label: "Submit",
  click() {
    console.log(`Clicked: ${this.label}`);
  },
};

button.click(); // OK
// const handler = button.click;
// handler();  // 였λ₯˜! this μ»¨ν…μŠ€νŠΈ 손싀

5. μ œλ„€λ¦­

5.1 μ œλ„€λ¦­ κΈ°λ³Έ

// μ œλ„€λ¦­ ν•¨μˆ˜
function identity<T>(arg: T): T {
  return arg;
}

// μ‚¬μš©
let output1 = identity<string>("hello");
let output2 = identity<number>(42);
let output3 = identity("auto"); // νƒ€μž… μΆ”λ‘ 

// μ œλ„€λ¦­ λ°°μ—΄
function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

const first = firstElement([1, 2, 3]); // number | undefined

5.2 μ œλ„€λ¦­ μΈν„°νŽ˜μ΄μŠ€μ™€ νƒ€μž…

// μ œλ„€λ¦­ μΈν„°νŽ˜μ΄μŠ€
interface Box<T> {
  value: T;
}

const stringBox: Box<string> = { value: "hello" };
const numberBox: Box<number> = { value: 42 };

// μ œλ„€λ¦­ νƒ€μž… 별칭
type Result<T> = {
  success: boolean;
  data: T;
};

type Pair<K, V> = {
  key: K;
  value: V;
};

const pair: Pair<string, number> = { key: "age", value: 30 };

5.3 μ œλ„€λ¦­ μ œμ•½μ‘°κ±΄

// extends둜 μ œμ•½ μΆ”κ°€
interface Lengthwise {
  length: number;
}

function logLength<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

logLength("hello"); // OK - string has length
logLength([1, 2, 3]); // OK - array has length
// logLength(123);    // 였λ₯˜! number has no length

// keyof μ œμ•½μ‘°κ±΄
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const person = { name: "Alice", age: 30 };
const name = getProperty(person, "name"); // string
const age = getProperty(person, "age"); // number
// getProperty(person, "email");  // 였λ₯˜!

5.4 μ œλ„€λ¦­ 클래슀

class Queue<T> {
  private items: T[] = [];

  enqueue(item: T): void {
    this.items.push(item);
  }

  dequeue(): T | undefined {
    return this.items.shift();
  }

  peek(): T | undefined {
    return this.items[0];
  }

  get length(): number {
    return this.items.length;
  }
}

const numberQueue = new Queue<number>();
numberQueue.enqueue(1);
numberQueue.enqueue(2);
console.log(numberQueue.dequeue()); // 1

6. μœ ν‹Έλ¦¬ν‹° νƒ€μž…

6.1 κΈ°λ³Έ μœ ν‹Έλ¦¬ν‹° νƒ€μž…

interface User {
  id: number;
  name: string;
  email: string;
  age?: number;
}

// Partial<T> - λͺ¨λ“  속성 μ„ νƒμ μœΌλ‘œ
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; age?: number }

// Required<T> - λͺ¨λ“  속성 ν•„μˆ˜λ‘œ
type RequiredUser = Required<User>;
// { id: number; name: string; email: string; age: number }

// Readonly<T> - λͺ¨λ“  속성 읽기 μ „μš©
type ReadonlyUser = Readonly<User>;

// Pick<T, K> - νŠΉμ • μ†μ„±λ§Œ 선택
type UserBasic = Pick<User, "id" | "name">;
// { id: number; name: string }

// Omit<T, K> - νŠΉμ • 속성 μ œμ™Έ
type UserWithoutEmail = Omit<User, "email">;
// { id: number; name: string; age?: number }

6.2 λ ˆμ½”λ“œμ™€ λ§΅ν•‘

// Record<K, T> - ν‚€-κ°’ λ§΅ν•‘
type UserRole = "admin" | "user" | "guest";
type RolePermissions = Record<UserRole, string[]>;

const permissions: RolePermissions = {
  admin: ["read", "write", "delete"],
  user: ["read", "write"],
  guest: ["read"],
};

// μ‚¬μš© μ˜ˆμ‹œ
type PageInfo = {
  title: string;
  url: string;
};

type Pages = Record<"home" | "about" | "contact", PageInfo>;

6.3 쑰건뢀 νƒ€μž…

// Exclude<T, U> - Tμ—μ„œ U μ œμ™Έ
type Numbers = 1 | 2 | 3 | 4 | 5;
type SmallNumbers = Exclude<Numbers, 4 | 5>; // 1 | 2 | 3

// Extract<T, U> - T와 U의 곡톡 νƒ€μž…
type Common = Extract<"a" | "b" | "c", "a" | "c" | "d">; // "a" | "c"

// NonNullable<T> - null, undefined μ œμ™Έ
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>; // string

// ReturnType<T> - ν•¨μˆ˜ λ°˜ν™˜ νƒ€μž…
function getUser() {
  return { id: 1, name: "Alice" };
}
type UserReturn = ReturnType<typeof getUser>;
// { id: number; name: string }

// Parameters<T> - ν•¨μˆ˜ λ§€κ°œλ³€μˆ˜ νƒ€μž…
type UserParams = Parameters<typeof getUser>; // []

6.4 ν…œν”Œλ¦Ώ λ¦¬ν„°λŸ΄ νƒ€μž…

// λ¬Έμžμ—΄ λ¦¬ν„°λŸ΄ μ‘°ν•©
type Color = "red" | "green" | "blue";
type Size = "small" | "medium" | "large";

type ClassName = `${Size}-${Color}`;
// "small-red" | "small-green" | ... | "large-blue"

// 이벀트 이름 생성
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"

7. μ—°μŠ΅ 문제

μ—°μŠ΅ 1: νƒ€μž… μ •μ˜

λ‹€μŒ 데이터 ꡬ쑰에 λŒ€ν•œ νƒ€μž…μ„ μ •μ˜ν•˜μ„Έμš”.

// μ˜ˆμ‹œ λ‹΅μ•ˆ
interface Product {
  id: number;
  name: string;
  price: number;
  category: string;
  inStock: boolean;
  tags?: string[];
}

interface CartItem {
  product: Product;
  quantity: number;
}

interface ShoppingCart {
  items: CartItem[];
  total: number;
  couponCode?: string;
}

μ—°μŠ΅ 2: μ œλ„€λ¦­ ν•¨μˆ˜

λ°°μ—΄μ—μ„œ 쑰건에 λ§žλŠ” 첫 번째 μš”μ†Œλ₯Ό μ°ΎλŠ” μ œλ„€λ¦­ ν•¨μˆ˜λ₯Ό μž‘μ„±ν•˜μ„Έμš”.

// μ˜ˆμ‹œ λ‹΅μ•ˆ
function find<T>(arr: T[], predicate: (item: T) => boolean): T | undefined {
  for (const item of arr) {
    if (predicate(item)) {
      return item;
    }
  }
  return undefined;
}

// μ‚¬μš©
const numbers = [1, 2, 3, 4, 5];
const firstEven = find(numbers, (n) => n % 2 === 0); // 2

const users = [{ name: "Alice" }, { name: "Bob" }];
const alice = find(users, (u) => u.name === "Alice");

μ—°μŠ΅ 3: μœ ν‹Έλ¦¬ν‹° νƒ€μž… ν™œμš©

API 응닡 νƒ€μž…μ„ μ •μ˜ν•˜μ„Έμš”.

// μ˜ˆμ‹œ λ‹΅μ•ˆ
interface ApiResponse<T> {
  success: boolean;
  data: T;
  error?: string;
  timestamp: number;
}

type User = {
  id: number;
  name: string;
  email: string;
};

type UserResponse = ApiResponse<User>;
type UsersResponse = ApiResponse<User[]>;
type DeleteResponse = ApiResponse<{ deleted: boolean }>;

// Partial을 ν™œμš©ν•œ μ—…λ°μ΄νŠΈ νƒ€μž…
type UserUpdate = Partial<Omit<User, "id">>;

λ‹€μŒ 단계

참고 자료

to navigate between lessons