HJS

Generics

제너릭은 타입을 변수처럼 사용할 수 있도록 해주는 TypeScript의 기능입니다. 이를 통해 함수, 클래스, 인터페이스 등에서 다양한 타입을 유연하게 처리할 수 있습니다.


1️⃣ 기본 개념

🔹 제너릭 함수

function identity<T>(arg: T): T {
  return arg;
}
console.log(identity<string>("Hello")); // "Hello"
console.log(identity<number>(42)); // 42

✔️ T타입 변수로 호출할 때 타입을 지정할 수 있습니다.
✔️ 함수 호출 시 타입을 명시적으로 지정하거나, 타입 추론이 가능합니다.




2️⃣ 활용 예시

🔹 제너릭 인터페이스

interface KeyValuePair<K, V> {
  key: K;
  value: V;
}
const kv1: KeyValuePair<string, number> = { key: "age", value: 30 };
const kv2: KeyValuePair<number, boolean> = { key: 1, value: true };

✔️ KV는 호출 시 타입을 동적으로 지정할 수 있습니다.


🔹 제너릭 클래스

class DataStorage<T> {
  private data: T[] = [];

  addItem(item: T) {
    this.data.push(item);
  }

  getItems(): T[] {
    return this.data;
  }

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

✔️ 제너릭을 활용해 다양한 타입의 데이터 저장소를 만들 수 있습니다.


🔹 제너릭 제한 (Constraints)

function logLength<T extends { length: number }>(arg: T): number {
  return arg.length
}

console.log(logLength("Hello")); // 5
console.log(logLength([1, 2, 3])); // 3

✔️ T extends { length: number }를 사용하여 특정 속성ㅇ르 가진 타입만 허용할 수 있습니다.


🔹 keyof와 함께 사용하기

function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

const person = { name: "Alice", age: 25 };
console.log(getProperty(person, "name")); // "Alice"

✔️ K extends keyof T 를 활용하면 객체의 키를 제한할 수 있습니다.


기본 타입 지정 (Default Type)

function createArray<T = string>(length: number, value: T): T[] {
  return new Array(length).fill(value)
}

✔️ <T = string>을 통해 기본 타입을 지정할 수 있습니다.




3️⃣ extends 키워드에 대한 자세한 설명

extends 키워드는 제너릭 타입에 특정 제약을 걸고 싶을 때 사용합니다.

🔹 기본적인 extends 사용법

function printName<T extends { name: string }>(obj: T){
  console.log(obj.name);
}

printName({ name: "Alice" }); // ✅ 정상
printName({ name: "Bob", age: 25 }); // ✅ 정상
//printName({ age: 30 }); // ❌ 오류: name 속성이 없음

✔️ T extends { name: string }반드시 name 속성이 포함된 객체만 허용합니다.


🔹 숫자 타입만 받도록 제한하기

function addNumber<T extends number>(a: T, b: T): number {
  return a + b;
}

console.log(addNumbers(5, 10)); // ✅ 정상
// console.log(addNumbers("5", "10")); // // ❌ 오류: 숫자가 아님

✔️ T extends number를 사용하면 숫자 타입만 받을 수 있도록 제한할 수 있습니다.


🔹 클래스에서 extends 활용하기

class Animal {
  constructor(public name: string) {}
}

class Dog extends Animal {
  bark() {
    console.log("Woof!");
  }
}

function makeNoise<T extends Animal>(animal: T) {
  console.log(`${animal.name} is making a noise`);
}

const dog = new Dog("Buddy");
makeNoise(dog); // ✅ 정상
// makeNoise({ name: "Charlie" }); // ❌ 오류: Animal 클래스를 확장하지 않음

✔️ T extends Animal을 사용하여 Animal 클래스를 확장한 객체만 허용할 수 있습니다.



4️⃣ 제너릭을 사용할 때 주의할 점

🔹 제너릭 타입의 제한적인 연산

function add<T>(a: T, b: T): T {
  return a + b; // ❌ 오류 발생
}

❗ 제너릭은 타입이 정확히 정의되지 않으므로, 연산을 수행할 수 없습니다.
✔️ 해결 방법: extends를 사용해 숫자 타입으로 제한해야 합니다.

🔹 남용하면 오히려 복잡해질 수 있음

❗ 너무 많은 제너릭을 사용하면 가독성이 떨어질 수 있습니다.
✔️ 단순한 경우에는 제너릭 없이 명확한 타입을 사용하는 것이 더 좋을 수 있습니다.