0%

타입 스크립트 제네릭

typescript-generics

Typescript 는 정적 타입 언어이기 때문이 함수 또는 클래스를 정의하는 시점에 매개변수나 반환값의 타입을 선언 해야한다. 그런데 함수 또는 클래스를 정의하는 시점에 매개변수나 반환값의 타입을 선언하기 어려운 경우가 있다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Queue{
protected data = []; // data :any[]
push(item){
this.data.push(item);
}
pop(){
return this.data.shift();
}
}
const queue = new Queue();

queue.push(0);
queue.push('0')//의도치 않은 실수

console.log(queue.pop().toFixed())// 0;
console.log(queue.pop().toFixed())// 런타임 에러

Queue 클래스의 data 프로터티는 타입 선언을 생략 하였기 때문에 any[]타입이 된다. any[]타입은 어떤 타입의 요소도 가질 수 있는 배열을 의미한다. any[]타입은 배열의 요소의 타입이 모두 같지 않다는 문제를 가지게 된다. 위 예제의 경우 data 프로퍼티는 number 타입만을 포함하는 배열이라는 기대 하에 각 요소에 대해
Number.prototype.toFixed를 사용하였다. 따라서 number 타입이 아닌 요소의 경우 런타입 에러가 발생한다.
위와 같은 문제를 해결하기 위해 Queue 클래스를 상속하여 number 타입 전용 NumberQueue 클래스를 정의 해보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Queue{
protected data = [];

push(item){
this.data.push(item)
}
pop(){
this.data.shift();
}
}
//Queue 클래스를 상속하여 number 타입 전용 NumberQueue 클래스 정의
class NumberQueue extends Queue{
//number타입의 요소만을 push한다.
push(item:number){
super.push(item);
}
pop():number{
return super.pop();
}
}
const queue = new NumberQueue();
queue.push(0);
// 의도하지 않은 실수를 사전 검출 가능
// [ts] Argument of type '"1"' is not assignable to parameter of type 'number'.
//quueue.push('0')
queue.push(+'0');
console.log(queue.pop().toFixed()); // 0

이와 같이 number 타입전용 NumberQueue 클래스를 정의하면 number 타입이외의 요소 추가(push)되었을 때 , 런타임 에러 이전에 에러를 사전에 감지 할 수 있다.

하지만 다양한 타입을 지원해야 한다면 타입 별로 클래스를 상속받아 추가 해야하므로 이 또한 좋은 방법은 아니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Queue<T>{
protected data :Array<T> = [];
push(item:T){
this.data.push(item);
}
pop():T{
return this.data.shift()p;
}
}
//number 전용 Queue
const numberQueue = new Queue<number>();
numberQueue.push(0);
// numberQueue.push('1'); // 의도하지 않은 실수를 사전 검출 가능
numberQueue.push(+'1'); // 실수를 사전 인지하고 수정할 수 있다

console.log(numberQueue.pop().toFixed()); // 0
console.log(numberQueue.pop().toFixed()); // 1

//string 전용 Queue
const stringQueue = new Queue<string>();

stringQueue.push('Hello');
stringQueue.push('World');

console.log(stringQueue.pop().toUpperCase()); // HELLO
console.log(stringQueue.pop().toUpperCase()); // WORLD


//커스텀 객체 전용 Queue
const myQueue = new Queue<{name:string,age:number}>();
myQueue.push({name:'kim',age:25});
myQueue.push({name:'park',age:20});

console.log(myQueue.pop()); // {name:'kim',age:25}
console.log(myQueue.pop()); // {name:'park',age:20}

제네릭은 선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입이 아닌 다양한 타입을 사용할 수 있도록 하는 기법이다. 한 번의 선언으로 다양한 타입에 재사용이 가능하다는 장점이 있다.

T는 제네릭을 선언할 때 관용적으로 사용되는 식별자로 타입 파라미터라 한다. T는 Type의 약자로 반드시 T을 사용해야하는 것은 아니다. 또한 함수에도 제네릭을 사용할 수 있다. 제네릭을 사용하면 하나의 타입이 아닌 다양한 타입의 매개 변수와 리턴 값을 사용할 수 있다.

1
2
3
function reverse<T>(items:T[]):T[]{
return items.reverse();
}

reverse함수는 인수의 타입에 의해 타입 매개변수가 결정이 된다 .Reverse함수는 다양한 타입의 요소로 구성된 배열을 인자로 받는다. 예를 들어 number 타입의 요소를 갖는 배열을 전달 받으면 타입 매개변수는 number 가 된다.

1
2
3
4
5
6
7
function reverse<T>(items:T[]):T[]{
return items.reverse();
}
const arg = [1,2,3,4,5];
//인수에 의해 타입 매개변수가 결정된다.
const reversed = reverse(arg);
console.log(reversed) //[5,4,3,2,1];

만약 {name:string }타입의 요소를 갖는 배열을 전달 받으면 타입 매개변수는 {name:string}가 된다.

1
2
3
4
5
6
7
function reverse<T>(items:T[]):T[]{
return items.reverse();
}
const args = [{name: 'kim'},{name: 'park'}];
//인수에 의해 타입 매개변수가 결정됨
const reversed = reverse(args);
console.log(reversed) // [{name: 'park'},{name: 'kim'}];