Generic(제네릭)
Generic(제네릭)은 함수, 클래스, 인터페이스 등의 타입을 일반화하여 재사용성을 높이는 기능이다. 즉, 타입스크립트에서 일반적으로 사용되는 타입을 구체적인 타입으로 대체하여 코드의 재사용성과 유연성을 높일 수 있다.
제네릭을 사용하면 함수나 클래스의 매개변수 또는 반환값의 타입을 미리 지정하지 않고, 실행 시점에 동적으로 타입을 결정할 수 있다.
이를 통해 다양한 타입의 인수를 전달하고 다양한 타입의 값을 반환하는 함수를 만들 수 있다.
제네릭은 대괄호(<>) 안에 타입 변수를 사용하여 정의된다. 이 타입 변수는 일반적으로 대문자로 표시되며, 사용자가 지정하는 타입이다.
예를 들어, 다음은 제네릭 함수를 정의하는 방법이다.
function identity<T>(arg: T): T {
return arg;
}
위 코드에서 T는 타입 변수이며, 함수 매개변수의 타입과 반환값의 타입을 일반화한다. arg 매개변수는 T 타입의 값을 받고, T 타입의 값을 반환한다. 이 함수를 사용하려면 T 타입을 명시해야 한다. 예를 들어, 다음과 같이 사용할 수 있다.
let output = identity<string>("hello world");
console.log(output); // "hello world"
제네릭 함수를 호출할 때 타입 변수를 생략할 수도 있다. 이 경우 타입스크립트는 인수의 타입으로부터 타입 변수를 유추한다.
let output = identity("hello world");
console.log(output); // "hello world"
타입 변수를 여러 개 사용하여 다중 제네릭을 구현할 수도 있다. 예를 들어, 다음은 두 개의 타입 변수를 사용하는 제네릭 함수이다.
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
위 함수는 [T, U] 타입의 튜플을 받아 [U, T] 타입의 튜플로 바꾸어 반환한다. 예를 들어, 다음과 같이 사용할 수 있다.
let result = swap([1, "hello"]);
console.log(result); // ["hello", 1]
제네릭은 클래스, 인터페이스 등에서도 사용할 수 있다. 예를 들어, 다음은 제네릭 클래스를 정의하는 방법이다.
class Queue<T> {
private items:
private count: number;
constructor() {
this.items = [];
this.count = 0;
}
enqueue(item: T) {
this.items.push(item);
this.count++;
}
dequeue(): T | undefined {
if (this.count === 0) {
return undefined;
}
this.count--;
return this.items.shift();
}
size(): number {
return this.count;
}
}
위 코드에서 Queue 클래스는 T 타입의 요소를 담는 큐를 구현한다. enqueue 메소드는 T 타입의 값을 큐에 추가하고, dequeue 메소드는 큐에서 요소를 꺼내어 T | undefined 타입으로 반환한다. size 메소드는 큐의 크기를 반환한다.
이 클래스를 사용하려면 타입 변수를 명시해야 한다. 예를 들어, 다음과 같이 사용할 수 있다.
let queue = new Queue<number>();
queue.enqueue(1);
queue.enqueue(2);
console.log(queue.size()); // 2
console.log(queue.dequeue()); // 1
console.log(queue.dequeue()); // 2
console.log(queue.dequeue()); // undefined
마지막으로, 제네릭은 타입스크립트에서 다형성을 구현하는 데 매우 중요한 역할을 한다.
다형성이란, 같은 인터페이스나 추상 클래스를 상속하는 다양한 하위 클래스가 있을 때, 상위 클래스의 메소드를 하위 클래스에서 오버라이딩하여 다양한 방식으로 구현하는 것을 말한다.
제네릭을 사용하여 인터페이스나 추상 클래스를 정의하면, 다양한 타입의 데이터를 다룰 수 있어 다형성을 구현하는 데 매우 유용하다.
제네릭 사용 예시
제네릭은 다양한 곳에서 사용될 수 있다. 일반적으로 제네릭은 컬렉션 데이터 타입이나 함수에서 많이 사용된다.
Collection Data Type(컬렉션 데이터 타입)
Collection Data Type(컬렉션 데이터 타입)은 제네릭을 사용하여 여러 타입의 데이터를 담을 수 있다. 예를 들어, 배열(Array)은 다음과 같이 정의될 수 있다.
let myArray: Array<number> = [1, 2, 3, 4, 5];
위 예제에서 Array<number>는 숫자 타입의 데이터만을 담을 수 있는 배열이다. 만약 다른 타입의 데이터를 담으려고 하면, 타입 에러가 발생한다.
Function(함수)
Function(함수)에서도 제네릭을 사용할 수 있다. 예를 들어, 다음과 같이 제네릭 함수를 정의할 수 있다.
function identity<T>(arg: T): T {
return arg;
}
위 예제에서 identity 함수는 매개변수와 반환값이 같은 타입인 T를 사용한다. 이 함수를 사용하면 다양한 타입의 값을 반환할 수 있다.
console.log(identity<string>("hello")); // "hello"
console.log(identity<number>(42)); // 42
Class(클래스)
Class(클래스)에서도 제네릭을 사용할 수 있다. 예를 들어, Stack 클래스를 다음과 같이 정의할 수 있다.
class Stack<T> {
private items: T[] = [];
push(item: T) {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
}
위 예제에서 Stack<T> 클래스는 T 타입의 값을 담는 스택을 구현한다. push 메소드는 T 타입의 값을 스택에 추가하고, pop 메소드는 스택에서 요소를 꺼내어 T | undefined 타입으로 반환한다. 이 클래스를 사용하면 다양한 타입의 값을 스택에 저장할 수 있다.
let stack = new Stack<number>();
stack.push(1);
stack.push(2);
console.log(stack.pop()); // 2
console.log(stack.pop()); // 1
위 예제에서 Stack<number>는 숫자 타입의 값을 담는 스택을 생성한다. 만약 문자열 타입의 값을 담으려고 하면, 타입 에러가 발생한다.