[typescript] 제네릭이란

verdantgreeny·2025년 3월 10일

본캠프

목록 보기
50/56

제네릭(Generic)

제네릭이란?

제네릭(Generic)은 타입을 마치 함수의 매개변수처럼 사용하는 기능이다. 이를 통해 코드의 재사용성을 높이고, 타입의 안정성을 유지할 수 있다.

일반적으로 타입을 지정하면 특정 타입의 값만 받을 수 있지만, 제네릭을 사용하면 다양한 타입을 처리할 수 있는 유연한 코드 작성이 가능하다.


제네릭을 사용하지 않는 경우

다음과 같은 함수들이 있다고 가정해 본다면,

function printStrings(arr: string[]): void {
    for (let i = 0; i < arr.length; i++) {
        console.log(arr[i]);
    }
}

function printNumbers(arr: number[]): void {
    for (let i = 0; i < arr.length; i++) {
        console.log(arr[i]);
    }
}

위 두 함수는 역할이 동일하지만, string[]number[]만 처리할 수 있다. 만약 boolean[]을 처리하는 함수가 필요하면 새로운 함수를 또 만들어야 한다. 중복 코드가 많아지는 단점이 있다.


제네릭을 활용한 코드 개선

제네릭을 사용하면 다음과 같이 하나의 함수로 여러 타입을 처리할 수 있다.

function printAnything<T>(arr: T[]): void {
    for (let i = 0; i < arr.length; i++) {
        console.log(arr[i]);
    }
}

제네릭 <T>를 사용하여 T[] 타입을 받도록 하면 string[], number[], boolean[] 등 다양한 타입을 처리할 수 있다.

printAnything(['a', 'b', 'c']); // T는 string으로 추론
printAnything([1, 2, 3]); // T는 number로 추론

위와 같이 타입을 명시적으로 지정하지 않아도 TypeScript가 자동으로 타입을 추론한다.


제네릭을 사용하는 React Hooks

React의 useState에서도 제네릭을 사용할 수 있다.

import { useState } from "react";

function App() {
  const [counter, setCounter] = useState<number>(1);
  const increment = () => {
    setCounter((prev) => prev + 1);
  };
  return <div onClick={increment}>{counter}</div>;
}

export default App;

useState<number>(1)과 같이 제네릭을 사용하면 counter의 타입이 number로 지정되며, setCounternumber 값을 받아야 한다.

그러나 TypeScript는 초기값 1number임을 자동으로 추론하기 때문에, 다음과 같이 작성해도 동일한 결과가 나온다.

const [counter, setCounter] = useState(1);

제네릭을 사용하지 않아도 타입이 자동으로 지정되지만, 경우에 따라 명시적으로 제네릭을 지정하는 것이 가독성을 높이는 데 도움이 될 수 있다.


제네릭 타입 추론

TypeScript는 제네릭 타입도 자동으로 추론할 수 있다.

function identity<T>(arg: T): T {
    return arg;
}

let output = identity("Hello"); // T는 string으로 추론

identity("Hello")를 호출하면 Tstring으로 추론되어 identity<string>(arg: string): string으로 동작한다.


제네릭의 실용적인 활용 예시

1. 제네릭 인터페이스

제네릭을 사용하여 인터페이스를 정의할 수 있다.

interface Box<T> {
    value: T;
}

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

2. 제네릭 함수의 다중 타입

제네릭을 여러 개 사용할 수도 있다.

function merge<T, U>(obj1: T, obj2: U): T & U {
    return { ...obj1, ...obj2 };
}

const mergedObj = merge({ name: "Alice" }, { age: 25 });

merge 함수는 두 개의 다른 타입을 받아 합친 후 반환한다.

3. 제네릭 클래스

제네릭을 클래스에서도 사용할 수 있다.

class DataStorage<T> {
    private data: T[] = [];
    
    addItem(item: T) {
        this.data.push(item);
    }
    
    removeItem(item: T) {
        this.data = this.data.filter(i => i !== item);
    }
    
    getItems() {
        return [...this.data];
    }
}

const textStorage = new DataStorage<string>();
textStorage.addItem("Hello");
textStorage.addItem("World");
textStorage.removeItem("Hello");
console.log(textStorage.getItems()); // ["World"]

결론

제네릭을 사용하면 타입을 유연하게 처리할 수 있으며, 코드의 재사용성을 높일 수 있다. 함수, 인터페이스, 클래스 등 다양한 곳에서 활용할 수 있으며, TypeScript의 타입 안정성을 유지하면서도 범용적인 코드를 작성할 수 있도록 도와준다.

0개의 댓글