함수 타입 정의
// 매개변수와 반환 타입
function add(a: number, b: number): number {
return a + b;
}
// 화살표 함수
const multiply = (a: number, b: number): number => a * b;
// 선택적 매개변수
function greet(name: string, greeting?: string): string {
return `${greeting ?? "안녕하세요"}, ${name}님!`;
}
// 기본값
function createUser(name: string, role: string = "user"): object {
return { name, role };
}
함수 타입 별칭
// 타입으로 함수 시그니처 정의
type MathOperation = (a: number, b: number) => number;
type Formatter = (value: string) => string;
const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;
// 콜백 타입
type EventHandler = (event: MouseEvent) => void;
type AsyncTask<T> = () => Promise<T>;
나머지 매개변수
function sum(...numbers: number[]): number {
return numbers.reduce((acc, n) => acc + n, 0);
}
sum(1, 2, 3); // 6
sum(1, 2, 3, 4, 5); // 15
제네릭 (Generics)
타입을 매개변수처럼 다루어 재사용 가능한 코드를 만듭니다.
// 제네릭 없이 — 타입별로 함수 중복
function getFirstString(arr: string[]): string {
return arr[0];
}
function getFirstNumber(arr: number[]): number {
return arr[0];
}
// 제네릭으로 — 하나의 함수로 모든 타입 처리
function getFirst<T>(arr: T[]): T {
return arr[0];
}
getFirst<string>(["a", "b", "c"]); // "a"
getFirst<number>([1, 2, 3]); // 1
getFirst(["a", "b"]); // 타입 추론: string
제네릭 제약 조건
// T는 반드시 length 속성을 가져야 함
function getLength<T extends { length: number }>(item: T): number {
return item.length;
}
getLength("hello"); // 5
getLength([1, 2, 3]); // 3
// getLength(42); // ❌ number는 length 없음
// keyof 제약
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "철수", age: 25 };
getProperty(user, "name"); // "철수"
// getProperty(user, "email"); // ❌ 'email'은 키가 아님
제네릭 인터페이스와 타입
// 제네릭 인터페이스
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
interface User {
id: number;
name: string;
}
const userResponse: ApiResponse<User> = {
data: { id: 1, name: "철수" },
status: 200,
message: "성공",
};
const listResponse: ApiResponse<User[]> = {
data: [{ id: 1, name: "철수" }],
status: 200,
message: "성공",
};
제네릭 유틸리티 패턴
// 페이지네이션 응답
interface Paginated<T> {
items: T[];
total: number;
page: number;
pageSize: number;
}
// 비동기 작업 상태
interface AsyncState<T> {
data: T | null;
loading: boolean;
error: Error | null;
}
// 사용
type UserListState = AsyncState<User[]>;
const state: UserListState = {
data: null,
loading: true,
error: null,
};
함수 오버로드
같은 함수가 입력 타입에 따라 다른 타입을 반환할 때 사용합니다.
// 오버로드 시그니처
function parse(value: string): number;
function parse(value: number): string;
// 구현 시그니처
function parse(value: string | number): number | string {
if (typeof value === "string") return parseInt(value);
return value.toString();
}
const num = parse("42"); // number
const str = parse(42); // string
실전: API 호출 래퍼
async function fetchApi<T>(url: string): Promise<ApiResponse<T>> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json() as Promise<ApiResponse<T>>;
}
// 타입 안전한 API 호출
const userData = await fetchApi<User>("/api/users/1");
// userData.data: User 타입으로 추론
console.log(userData.data.name);
정리
| 개념 | 예시 | 용도 |
|---|---|---|
| 함수 타입 | (a: number) => string | 함수 구조 정의 |
| 선택적 매개변수 | name?: string | 있어도 없어도 됨 |
| 제네릭 | <T> | 재사용 가능한 타입 |
| 제약 조건 | T extends X | 제네릭 범위 제한 |
keyof | K extends keyof T | 객체 키 타입 |
| 오버로드 | 시그니처 + 구현 | 다형성 함수 |
다음 편에서는 클래스와 접근 제어자 — TypeScript로 객체지향 프로그래밍을 하는 방법을 배웁니다.