TypeScript 함수의 타입 시스템 완전 이해
매개변수 (Parameter, 함수에 전달하는 입력값)와 반환 타입 (Type)을 명시적으로 지정합니다.
// 매개변수 타입 + 반환 타입
function add(a: number, b: number): number {
return a + b;
}
// 반환 타입 추론 (명시하지 않아도 OK)
function multiply(a: number, b: number) {
return a * b; // number로 자동 추론
}
// 문자열을 반환하는 함수
function greet(name: string): string {
return `안녕하세요, ${name}님!`;
}
// 타입 에러 예시
add("1", 2); // Error! string은 number에 할당 불가
add(1, 2, 3); // Error! 매개변수 2개만 허용
greet(42); // Error! number는 string에 할당 불가
// 기본 화살표 함수: (매개변수): 반환타입 => { 본문 }
const add = (a: number, b: number): number => {
return a + b;
};
// 한 줄 표현식 (중괄호와 return 생략 가능)
const double = (n: number): number => n * 2;
// 함수 타입을 변수에 먼저 선언하고, 나중에 구현
const greet: (name: string) => string = (name) => {
return `Hello, ${name}!`;
};
// 타입 별칭 (Type Alias)으로 함수 타입 정의 (권장)
type MathOperation = (a: number, b: number) => number;
const subtract: MathOperation = (a, b) => a - b;
const divide: MathOperation = (a, b) => a / b;
// 함수 배열도 가능
const operations: MathOperation[] = [add, subtract, divide];
operations.forEach(op => console.log(op(10, 3)));
// ? 를 붙이면 선택적 매개변수 (호출 시 생략 가능)
function greet(name: string, greeting?: string): string {
// greeting의 타입: string | undefined (값이 없을 수 있음)
return `${greeting || "안녕하세요"}, ${name}님!`;
}
greet("홍길동"); // "안녕하세요, 홍길동님!"
greet("홍길동", "반갑습니다"); // "반갑습니다, 홍길동님!"
// 여러 선택적 매개변수
function createUser(
name: string,
age?: number,
email?: string
): void {
console.log(name);
if (age !== undefined) console.log(`나이: ${age}`);
if (email !== undefined) console.log(`이메일: ${email}`);
}
function bad(x?: number, y: number)는 에러입니다.
// 기본값이 있으면 타입을 추론하고, 생략 가능
function greet(name: string, greeting: string = "안녕하세요"): string {
return `${greeting}, ${name}님!`;
}
greet("홍길동"); // "안녕하세요, 홍길동님!"
greet("홍길동", "환영합니다"); // "환영합니다, 홍길동님!"
// 기본값에서 타입 추론
function createConfig(
port: number = 3000,
host: string = "localhost",
debug: boolean = false
) {
return { port, host, debug };
}
// 기본값은 중간에도 올 수 있음 (undefined로 건너뛰기)
function format(
value: number,
prefix: string = "$",
suffix: string = ""
): string {
return `${prefix}${value}${suffix}`;
}
format(100); // "$100"
format(100, undefined, "원"); // "$100원"
format(100, "\\", ""); // "\\100"
// ...args로 가변 인수 받기 (개수 제한 없이 여러 개를 배열로 받음)
function sum(...numbers: number[]): number {
return numbers.reduce((total, n) => total + n, 0); // 모든 숫자를 더함
}
sum(1, 2, 3); // 6
sum(1, 2, 3, 4, 5); // 15
sum(); // 0
// 일반 매개변수 + 나머지 매개변수
function log(level: string, ...messages: string[]): void {
console.log(`[${level}]`, ...messages);
}
log("INFO", "서버 시작", "포트: 3000");
log("ERROR", "연결 실패");
// 튜플과 함께 사용
function createUser(
name: string,
...info: [age: number, email: string]
): void {
const [age, email] = info;
console.log(`${name}, ${age}, ${email}`);
}
createUser("Kim", 25, "kim@example.com");
같은 함수명으로 다른 매개변수 타입에 따라 다른 반환 타입을 제공합니다.
하나의 함수가 여러 가지 "사용 설명서"를 가지는 것과 같습니다.
// 오버로드 시그니처 (Signature, 함수의 입출력 형태 선언)
function createElement(tag: "a"): HTMLAnchorElement;
function createElement(tag: "canvas"): HTMLCanvasElement;
function createElement(tag: "input"): HTMLInputElement;
function createElement(tag: string): HTMLElement;
// 구현부 (실제 로직)
function createElement(tag: string): HTMLElement {
return document.createElement(tag);
}
// 호출 시 반환 타입이 정확하게 추론
const link = createElement("a"); // HTMLAnchorElement
const canvas = createElement("canvas"); // HTMLCanvasElement
const div = createElement("div"); // HTMLElement
link.href = "https://example.com"; // OK (HTMLAnchorElement)
canvas.getContext("2d"); // OK (HTMLCanvasElement)
// 입력 타입에 따라 반환 타입이 달라지는 경우
function parseValue(value: string): number;
function parseValue(value: string[]): number[];
function parseValue(value: string | string[]): number | number[] {
if (Array.isArray(value)) {
return value.map(v => parseInt(v, 10));
}
return parseInt(value, 10);
}
const single = parseValue("42"); // number
const multiple = parseValue(["1", "2"]); // number[]
// 매개변수 개수가 다른 오버로드
function formatDate(timestamp: number): string;
function formatDate(year: number, month: number, day: number): string;
function formatDate(yearOrTimestamp: number, month?: number, day?: number): string {
if (month !== undefined && day !== undefined) {
return `${yearOrTimestamp}-${month}-${day}`;
}
return new Date(yearOrTimestamp).toISOString();
}
formatDate(1700000000000); // "2023-11-14T..."
formatDate(2024, 1, 15); // "2024-1-15"
// void: "반환값을 사용하지 않겠다"
function logMessage(msg: string): void {
console.log(msg);
}
// 콜백에서의 void는 특별함
type Callback = () => void;
// 반환값이 있어도 무시됨!
const cb: Callback = () => {
return 42; // OK! void 콜백이라 반환값 무시
};
// void 콜백의 반환값은 사용 불가
const result: void = cb();
// result를 숫자처럼 사용할 수 없음
// undefined: 정확히 undefined를 반환
function nothing(): undefined {
return undefined; // 반드시 명시
}
// return 없으면 에러
function bad(): undefined {
// Error! 반환문이 필요
}
// 실제 차이점 예시
const arr = [1, 2, 3];
// forEach 콜백은 void 반환
arr.forEach((n): void => {
console.log(n);
// return 있어도 OK
});
// map에서는 반환 타입이 중요
arr.map((n): number => {
return n * 2;
});
콜백이란 "나중에 호출해줘"라고 다른 함수에 전달하는 함수입니다. 특정 작업이 끝난 후 실행됩니다.
// 콜백 타입 정의
type SuccessCallback = (data: string) => void;
type ErrorCallback = (error: Error) => void;
function fetchData(
url: string,
onSuccess: SuccessCallback,
onError: ErrorCallback
): void {
try {
// ... 데이터 가져오기
onSuccess("데이터 로드 완료!");
} catch (e) {
onError(e as Error);
}
}
// 사용 예시
fetchData(
"/api/users",
(data) => console.log(data), // data: string
(error) => console.error(error) // error: Error
);
// 이벤트 핸들러 타입
type EventHandler = (event: { type: string; target: HTMLElement }) => void;
const handleClick: EventHandler = (event) => {
console.log(`${event.type} on ${event.target.tagName}`);
};
함수를 매개변수로 받거나 반환하는 함수입니다.
마치 "함수를 만들어주는 공장"처럼, 함수 자체를 값으로 다룹니다.
// 함수를 반환하는 함수
function createMultiplier(factor: number): (n: number) => number {
return (n: number) => n * factor;
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
double(5); // 10
triple(5); // 15
// 함수를 매개변수로 받는 함수
function applyToArray<T, U>(
arr: T[],
fn: (item: T, index: number) => U
): U[] {
return arr.map(fn);
}
const numbers = [1, 2, 3, 4];
const strings = applyToArray(numbers, (n) => n.toString());
// strings: string[] = ["1", "2", "3", "4"]
const doubled = applyToArray(numbers, (n) => n * 2);
// doubled: number[] = [2, 4, 6, 8]
제네릭은 타입을 매개변수로 받아, 다양한 타입에서 재사용 가능한 함수를 만듭니다.
비유하면, "어떤 타입이든 담을 수 있는 만능 상자"를 만드는 것과 같습니다.
// 문제: 모든 타입에 대해 동일한 로직을 반복해야 함
function firstString(arr: string[]): string | undefined {
return arr[0];
}
function firstNumber(arr: number[]): number | undefined {
return arr[0];
}
// 해결: 제네릭으로 한 번만 작성 (<T>가 "타입 매개변수")
function first<T>(arr: T[]): T | undefined {
return arr[0]; // T가 어떤 타입이든 첫 번째 요소 반환
}
// 타입이 자동 추론됨 (전달한 배열의 타입을 보고 T를 결정)
const a = first(["hello", "world"]); // T=string -> string | undefined
const b = first([1, 2, 3]); // T=number -> number | undefined
// 명시적으로 타입 지정도 가능
const c = first<boolean>([true, false]); // T=boolean -> boolean | undefined
// 여러 타입 매개변수
function pair<A, B>(first: A, second: B): [A, B] {
return [first, second];
}
const p = pair("hello", 42); // [string, number]
// 제네릭 + 제약 조건
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Kim", age: 25 };
const name = getProperty(user, "name"); // string
const age = getProperty(user, "age"); // number
// getProperty(user, "email"); // Error! "email"은 keyof User가 아님
// 제네릭 유틸리티 함수
function toArray<T>(value: T | T[]): T[] {
return Array.isArray(value) ? value : [value];
}
toArray("hello"); // string[]
toArray([1, 2, 3]); // number[]
// 패턴 1: 옵션 객체 매개변수 (설정값을 객체로 묶어서 전달)
interface FetchOptions {
method?: "GET" | "POST" | "PUT" | "DELETE"; // HTTP 요청 방식
headers?: Record<string, string>; // 요청 헤더 (Record: 키-값 쌍의 객체 타입)
body?: string; // 요청 본문
timeout?: number; // 제한 시간 (밀리초)
function fetchAPI(url: string, options: FetchOptions = {}): Promise<Response> {
const { method = "GET", timeout = 5000 } = options;
return fetch(url, { ...options, method });
}
// 패턴 2: 빌더 패턴의 체이닝
function createQueryBuilder() {
let query = "";
return {
select(fields: string[]) { query += `SELECT ${fields.join(", ")} `; return this; },
from(table: string) { query += `FROM ${table} `; return this; },
where(condition: string) { query += `WHERE ${condition} `; return this; },
build(): string { return query.trim(); }
};
}
const sql = createQueryBuilder()
.select(["name", "age"])
.from("users")
.where("age > 18")
.build();
| 개념 | 문법 | 설명 |
|---|---|---|
| 타입 어노테이션 | (a: number): string | 매개변수 + 반환 타입 명시 |
| 화살표 함수 | (n: number) => n * 2 | 간결한 함수 표현식 |
| 선택적 매개변수 | name?: string | 생략 가능 (| undefined) |
| 기본값 | port = 3000 | 생략 시 기본값 사용 |
| 나머지 매개변수 | ...args: number[] | 가변 인수 |
| 오버로드 | 여러 시그니처 선언 | 입력에 따라 반환 타입 변경 |
| void | (): void | 반환값 사용하지 않음 |
| 콜백 타입 | type Fn = (x: T) => R | 함수 타입 별칭 |
| 제네릭 | <T>(arr: T[]): T | 타입을 매개변수로 |