[TypeScript] 제네릭

🧩 제네릭의 정의와 기본 사용법

제네릭은 타입스크립트에서 다양한 타입에 대해 재사용 가능한 컴포넌트를 만들 수 있게 해주는 기능이다. 제네릭을 사용하면 특정 타입에 종속되지 않고 여러 타입에서 동작하는 컴포넌트를 작성할 수 있다.

제네릭을 사용하지 않으면 각 타입마다 별도의 함수를 작성해야 하지만, 제네릭을 사용하면 하나의 함수로 여러 타입을 처리할 수 있다.

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

let output1 = identity<string>("myString"); // 타입: string
let output2 = identity<number>(100); // 타입: number

이 예제에서 <T>는 타입 변수로, 사용자가 제공한 타입으로 대체된다.


🔄 제네릭 함수

제네릭 함수는 다양한 타입의 인자를 받아 작업할 수 있는 함수를 만들 수 있게 해준다. 이를 통해 코드 중복을 줄이고 타입 안정성을 유지할 수 있다.

function reverseArray<T>(array: T[]): T[] {
  return array.reverse();
}

const numbers = reverseArray<number>([1, 2, 3, 4, 5]);
const strings = reverseArray<string>(["a", "b", "c", "d"]);

📜 제네릭 인터페이스

인터페이스에도 제네릭을 사용할 수 있다. 이를 통해 재사용 가능한 인터페이스를 정의할 수 있다. 제네릭 인터페이스는 다양한 타입에 대해 동일한 구조를 가진 객체를 정의할 때 유용하다.

interface GenericIdentityFn<T> {
  (arg: T): T;
}

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

let myIdentity: GenericIdentityFn<number> = identity;

이 예제에서 GenericIdentityFn 인터페이스는 어떤 타입 T에 대해서도 동작하는 함수의 구조를 정의한다.


🏗️ 제네릭 클래스

클래스에서도 제네릭을 사용하여 재사용 가능한 컴포넌트를 만들 수 있다. 제네릭 클래스는 클래스의 프로퍼티나 메서드가 여러 타입에서 작동해야 할 때 유용하다.

class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;

  constructor(zero: T, addFn: (x: T, y: T) => T) {
    this.zeroValue = zero;
    this.add = addFn;
  }
}

let numberCalculator = new GenericNumber<number>(0, (x, y) => x + y);
console.log(numberCalculator.add(5, 10)); // 출력: 15

let stringCalculator = new GenericNumber<string>("", (x, y) => x + y);
console.log(stringCalculator.add("Hello, ", "World!")); // 출력: "Hello, World!"

이 예제에서 GenericNumber 클래스는 숫자와 문자열 모두에 대해 작동한다.


🏷️ 제네릭 타입 별칭

타입 별칭에서도 제네릭을 사용할 수 있다. 이를 통해 재사용 가능한 타입을 정의할 수 있다. 제네릭 타입 별칭은 복잡한 타입을 간단하게 표현하고 재사용할 때 유용하다.

type GenericArray<T> = Array<T>;

const numberArray: GenericArray<number> = [1, 2, 3];
const stringArray: GenericArray<string> = ["a", "b", "c"];

type GenericFunc<T> = (arg: T) => T;

const echoNumber: GenericFunc<number> = (x) => x;
const echoString: GenericFunc<string> = (x) => x;

이 예제에서 GenericArrayGenericFunc는 다양한 타입에 대해 재사용할 수 있는 타입 별칭이다.


🔒 제네릭 타입 제약

제네릭 타입에 특정 조건을 부여하여 사용할 수 있는 타입을 제한할 수 있다. 이는 특정 프로퍼티나 메서드가 존재하는 타입만 사용하고 싶을 때 유용하다.

interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length); // 이제 .length 속성이 있다고 확신할 수 있다
  return arg;
}

loggingIdentity([1, 2, 3]); // 성공
loggingIdentity({ length: 10, value: 3 }); // 성공
loggingIdentity(3); // 오류, number에는 .length가 없다

이 예제에서 T extends LengthwiseTLengthwise 인터페이스를 확장해야 한다는 것을 의미한다. 즉, T는 반드시 length 프로퍼티를 가져야 한다.


🔑 keyof 연산자와 제네릭

keyof 연산자를 제네릭과 함께 사용하여 객체의 속성에 안전하게 접근할 수 있다. 이는 특정 객체의 속성 이름을 타입으로 가져와 해당 속성에 대한 타입 안전성을 제공한다.

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

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, "a"); // 성공
getProperty(x, "m"); // 오류: 인수의 타입 '"m"'은 '"a" | "b" | "c" | "d"' 타입의 매개변수에 할당될 수 없다.

이 기능을 사용하면, 객체의 속성을 동적으로 접근하면서도 타입스크립트의 타입 검사를 통해 오류를 예방할 수 있다. 이를 통해 더욱 안전하고 재사용 가능한 코드를 작성할 수 있다.


제네릭은 타입스크립트에서 유연하고 재사용 가능한 코드를 작성하는 데 매우 유용한 기능이다.

제네릭을 잘 활용하면 타입 안정성을 유지하면서도 다양한 타입에 대응할 수 있는 컴포넌트를 만들 수 있다. 이를 통해 코드의 재사용성이 높아지고, 타입 관련 버그를 줄일 수 있다.

Categories:

Updated:

Leave a comment