제네릭 타입에 대해 알아보고 어떻게 사용할 수 있는지,
그리고 배열을 이용하지 않고 Queue자료구조를 구현하면서 제네릭을 어떻게 실제로 사용할 수 있는지 알아보자.
아래의 내용은 타입스크립트 공식문서 내용을 참고하였다.
제네릭타입의 필요성
우리가 서버를 띄울 때 항상 "Hello World"를 먼저 출력하는 것 처럼 제네릭에서의 "Hello World"인 identity 함수를 통해 발걸음을 시작해보자.
identity함수는 받은 인수를 그대로 리턴하는 매우 간단한 함수이다.
만약 제네릭을 사용하지 않는다면 우리는 특정 타입을 지정해주거나, any를 사용해야 한다.
하지만 앞선 글에서 말한 것처럼 any는 실제로 함수가 반환할 때 어떤 타입인지에 대한 정보가 전혀 없게 되어 타입스크립트를 쓰는 의미가 사라지게 되므로 사용을 자제해야 한다.(any를 쓴다면 그냥 JS를 쓰는 것과 차이가 무엇인가!)
function identity(args: number):number{
return args
}
function identity(args: any):any{
return args
}
만약, any를 사용하게 된다면 우리가 string 타입을 인수로 넘긴다고 하더라도 any 타입으로 리턴된다는 것 밖에 알지 못한다.
따라서 우리는 인수의 타입을 캡쳐할 필요가 생기는 것이다.
제네릭타입의 사용
function identity<T>(args:T):T{
return args
}
identity 함수에 T라는 변수를 추가했다.(여기서 T는 Type의 약자로 사용했지만, K,E, V, ... 아무 변수나 사용이 가능하다. )
우리는 이 T라는 변수를 통해 인자로 들어온 타입(e.g. string)을 캡쳐하고, 이 정보를 나중에 사용할 수 있게 된다.
여기에서는 T타입이 리턴 된다는 것을 알려주기 위해 사용된다.
이렇게 제네릭 identity 함수를 통해 타입을 불문하고 일반적으로 사용가능 하게 되었다.
제네릭 함수의 호출 방법
이렇게 작성된 제네릭 함수는 두가지 방법으로 호출이 가능하다.
1. 명시적 선언방법
let output = identity<string>("myString"); // 출력 타입은 'string'입니다.
2. 타입인수 추론 방법
let output = identity("myString"); // 출력 타입은 'string'입니다.
제네릭 함수의 실제 의미
그럼 실제로 제네릭을 사용하여 작성된 함수를 어떤 방식으로 읽으면 좋을지 알아보자.
아래 코드를 살펴보자.
function consoleLength<T>(args: T[]): T[] {
console.log(args.length);
return args;
}
consoleLength 라는 함수는 인수의 길이를 콘솔로 출력하고 인수를 그대로 리턴하는 함수이다.
이제 이 함수를 읽어보자.
제네릭 함수 consoleLength는 타입 매개변수 T와 T type 배열인 args를 인자로 받고 T type배열의 값을 리턴한다. 라고 읽을 수 있다.
제네릭의 실제 사용: Queue 구현
제네릭 타입을 이용하여 Queue자료 구조를 만들어보자.
물론, JS에서 제공하는 배열을 이용하면 매우매우매우 손쉽게 만들 수 있지만, array 자료타입을 사용하지 않고, 구현 해보도록 하자.
{
interface Queue<T> {
readonly total: number;
enqueue(value: T): void;
dequeue(): T;
}
type Node<T> = {
value: T;
after?: Node<T>;
};
class QueueImple<T> implements Queue<T> {
private _total: number = 0;
private first?: Node<T>;
private cur?: Node<T>;
constructor(private capacity: number) {
this.capacity = capacity;
}
get total(): number {
return this._total;
}
enqueue(value: T): void {
if (this.total === this.capacity) {
throw new Error('Your capacity is already full');
}
const node: Node<T> = { value };
//첫번째 노드라면 first에 세팅
if (!this.first) {
this.first = node;
this.cur = node;
} else {
this.cur!.after = node;
this.cur = node;
}
this._total++;
}
dequeue(): T {
if (this.first === null) {
throw new Error('your queue is already empty!');
}
const beforeNode = this.first;
this.first = this.first?.after;
this._total--;
return beforeNode!.value;
}
}
const queue = new QueueImple<string>(10);
queue.enqueue('mark, 1');
queue.enqueue('bob, 2');
queue.enqueue('oliv, 3');
while (queue.total !== 0) {
console.log(queue.dequeue());
}
const queue2 = new QueueImple<number>(10);
queue2.enqueue(123);
queue2.enqueue(456);
queue2.enqueue(789);
while (queue2.total !== 0) {
console.log(queue2.dequeue());
}
}
제네릭 타입을 이용해서, queue에 number나 string과 같은 여러 타입을 넣을 수 있도록 만들어 보았다.
참고로 위 코드의 출력값은 아래와 같을 것이다.
마무리
TS를 배우기 전에는 도대체 무슨 뜻인지 궁금헀던 제네릭 타입에 대해 알아봤다.
이전에는 공식문서를 보거나 함수설명을 볼 때 나오는 <T>와 같은 것들이 도대체 T가 무엇인지 V가 무엇인지 궁금했는데, 이번 학습을 통해 한층 더 문서를 잘 읽을 수 있게 된 것 같아 기분이 좋다.
실제로 프로젝트에서도 적극적으로 적용해봐야 겠다.
'TypeScript' 카테고리의 다른 글
"Property '...' does not exist on type 'EventTarget'" in TypeScript (0) | 2022.05.21 |
---|---|
[TypeScript] Cannot write file '....' because it would overwrite input file. (0) | 2022.05.02 |
6. TypeScript - composition (0) | 2022.04.14 |
5. TypeScript - OOP의 원칙과 실제 코드 예시 (0) | 2022.04.14 |
4. TypeScript - Types (0) | 2022.04.11 |