TypeScriptTypeScript 기초 · 4기초

함수와 제네릭 — 재사용 가능한 타입 안전 코드

TypeScript함수제네릭generic오버로드

함수 타입 정의

// 매개변수와 반환 타입
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제네릭 범위 제한
keyofK extends keyof T객체 키 타입
오버로드시그니처 + 구현다형성 함수

다음 편에서는 클래스와 접근 제어자 — TypeScript로 객체지향 프로그래밍을 하는 방법을 배웁니다.

궁금한 점이 있으신가요?

협업·의뢰는 아래로, 가벼운 소통은 인스타그램 @bluefox._.hi도 환영이에요.